]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
rust/smb: initial support
authorVictor Julien <victor@inliniac.net>
Mon, 26 Feb 2018 12:39:42 +0000 (13:39 +0100)
committerVictor Julien <victor@inliniac.net>
Mon, 12 Mar 2018 14:34:42 +0000 (15:34 +0100)
Implement SMB app-layer parser for SMB1/2/3. Features:
- file extraction
- eve logging
- existing dce keyword support
- smb_share/smb_named_pipe keyword support (stickybuffers)
- auth meta data extraction (ntlmssp, kerberos5)

45 files changed:
rules/smb-events.rules [new file with mode: 0644]
rust/Cargo.toml.in
rust/gen-c-headers.py
rust/src/filetracker.rs
rust/src/lib.rs
rust/src/smb/auth.rs [new file with mode: 0644]
rust/src/smb/dcerpc.rs [new file with mode: 0644]
rust/src/smb/dcerpc_records.rs [new file with mode: 0644]
rust/src/smb/debug.rs [new file with mode: 0644]
rust/src/smb/detect.rs [new file with mode: 0644]
rust/src/smb/events.rs [new file with mode: 0644]
rust/src/smb/files.rs [new file with mode: 0644]
rust/src/smb/log.rs [new file with mode: 0644]
rust/src/smb/mod.rs [new file with mode: 0644]
rust/src/smb/nbss_records.rs [new file with mode: 0644]
rust/src/smb/ntlmssp_records.rs [new file with mode: 0644]
rust/src/smb/smb.rs [new file with mode: 0644]
rust/src/smb/smb1.rs [new file with mode: 0644]
rust/src/smb/smb1_records.rs [new file with mode: 0644]
rust/src/smb/smb2.rs [new file with mode: 0644]
rust/src/smb/smb2_records.rs [new file with mode: 0644]
src/Makefile.am
src/app-layer-smb-tcp-rust.c [new file with mode: 0644]
src/app-layer-smb-tcp-rust.h [new file with mode: 0644]
src/app-layer-smb.c
src/app-layer-smb.h
src/detect-dce-iface.c
src/detect-dce-opnum.c
src/detect-dce-stub-data.c
src/detect-engine-register.c
src/detect-engine-register.h
src/detect-file-data.c
src/detect-filename.c
src/detect-smb-share.c [new file with mode: 0644]
src/detect-smb-share.h [new file with mode: 0644]
src/output-json-alert.c
src/output-json-smb.c [new file with mode: 0644]
src/output-json-smb.h [new file with mode: 0644]
src/output.c
src/rust.h
src/suricata-common.h
src/util-error.c
src/util-error.h
src/util-profiling.c
suricata.yaml.in

diff --git a/rules/smb-events.rules b/rules/smb-events.rules
new file mode 100644 (file)
index 0000000..618e357
--- /dev/null
@@ -0,0 +1,14 @@
+# SMB app layer event rules
+#
+# SID's fall in the 2225000+ range. See https://redmine.openinfosecfoundation.org/projects/suricata/wiki/AppLayer
+#
+# These sigs fire at most once per connection.
+#
+
+alert smb any any -> any any (msg:"SURICATA SMB internal parser error"; flow:to_server; app-layer-event:smb.internal_error; classtype:protocol-command-decode; sid:2225000; rev:1;)
+alert smb any any -> any any (msg:"SURICATA SMB internal parser error"; flow:to_client; app-layer-event:smb.internal_error; classtype:protocol-command-decode; sid:2225001; rev:1;)
+
+alert smb any any -> any any (msg:"SURICATA SMB malformed request data"; flow:to_server; app-layer-event:smb.malformed_data; classtype:protocol-command-decode; sid:2225002; rev:1;)
+alert smb any any -> any any (msg:"SURICATA SMB malformed response data"; flow:to_client; app-layer-event:smb.malformed_data; classtype:protocol-command-decode; sid:2225003; rev:1;)
+
+alert smb any any -> any any (msg:"SURICATA SMB malformed NTLMSSP record"; flow:to_server; app-layer-event:smb.malformed_ntlmssp_request; classtype:protocol-command-decode; sid:2225004; rev:1;)
index 9cd079f7a227615d3f3215b74a692b3988b92441..55ae38997c699b366da2affa06b897587f86531a 100644 (file)
@@ -18,5 +18,6 @@ debug = []
 nom = "~3.2.1"
 libc = "^0.2.36"
 crc = "~1.7.0"
+der-parser = "0.5.0"
 
 ntp-parser = { version = "^0", optional = true }
index a6e803a9bac336db62b29e508689adf1557037c4..d0b737222f6db0769f3504f58c5fe95c5fe47181 100755 (executable)
@@ -82,6 +82,8 @@ type_map = {
     "NTPTransaction": "NTPTransaction",
     "TFTPTransaction": "TFTPTransaction",
     "TFTPState": "TFTPState",
+    "SMBState": "SMBState",
+    "SMBTransaction": "SMBTransaction",
     "JsonT": "json_t",
     "DetectEngineState": "DetectEngineState",
     "core::DetectEngineState": "DetectEngineState",
index aa9acfa63abf336ddecdfadb36ddc249dcc548ff..e393a8bbf2fc58cd77cabc7a6a79e7c2655e7a08 100644 (file)
@@ -106,6 +106,7 @@ impl FileTransferTracker {
 
     pub fn close(&mut self, files: &mut FileContainer, flags: u16) {
         if !self.file_is_truncated {
+            SCLogDebug!("closing file with id {}", self.track_id);
             files.file_close(&self.track_id, flags);
         }
         self.file_open = false;
index b322c08b685debbb08b67bb33fc719de3044c183..c2d7ff99bb7b753c10b086a603e19275f20f7a17 100644 (file)
@@ -24,6 +24,8 @@ extern crate nom;
 
 extern crate crc;
 
+extern crate der_parser;
+
 #[macro_use]
 pub mod log;
 
@@ -44,6 +46,7 @@ pub mod lua;
 pub mod dns;
 pub mod nfs;
 pub mod ftp;
+pub mod smb;
 
 #[cfg(feature = "experimental")]
 pub mod ntp;
diff --git a/rust/src/smb/auth.rs b/rust/src/smb/auth.rs
new file mode 100644 (file)
index 0000000..6eca860
--- /dev/null
@@ -0,0 +1,439 @@
+/* Copyright (C) 2018 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use log::*;
+use smb::ntlmssp_records::*;
+use smb::smb::*;
+
+use nom;
+use nom::{IResult, ErrorKind};
+use der_parser;
+
+#[derive(Debug,PartialEq)]
+pub struct Kerberos5Ticket {
+    pub realm: Vec<u8>,
+    pub snames: Vec<Vec<u8>>,
+}
+
+/// ticket starts with custom header [APPLICATION 1]
+fn parse_kerberos5_request_ticket(blob: &[u8]) -> IResult<&[u8], Kerberos5Ticket>
+{
+    let (rem, ticket_hdr) = match der_parser::der_read_element_header(blob) {
+        IResult::Done(rem, o) => (rem, o),
+        IResult::Incomplete(needed) => {  return IResult::Incomplete(needed); },
+        IResult::Error(err) => { return IResult::Error(err); },
+    };
+    SCLogDebug!("parse_kerberos5_request_ticket: ticket {:?}, remaining data {}", ticket_hdr, rem.len());
+
+    if !(ticket_hdr.class == 1 && ticket_hdr.structured == 1 && ticket_hdr.tag == 1 && ticket_hdr.len == rem.len() as u64) {
+        SCLogDebug!("parse_kerberos5_request_ticket: bad data");
+        return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_KRB_FMT_ERR)));
+    }
+    let (_, ticket_seq) = match der_parser::parse_der_sequence(rem) {
+        IResult::Done(rem, o) => (rem, o),
+        IResult::Incomplete(needed) => { return IResult::Incomplete(needed); },
+        IResult::Error(err) => { return IResult::Error(err); },
+    };
+    SCLogDebug!("parse_kerberos5_request_ticket: ticket {:?}", ticket_seq);
+
+    let ticket_vec = ticket_seq.as_sequence().unwrap(); // parse_der_sequence is checked
+    SCLogDebug!("parse_kerberos5_request_ticket: ticket_vec {:?}", ticket_vec);
+    if ticket_vec.len() != 4 {
+        SCLogDebug!("parse_kerberos5_request_ticket: unexpected format");
+        return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_KRB_FMT_ERR)));
+    }
+
+    SCLogDebug!("parse_kerberos5_request_ticket: tkt-vno {:?}", ticket_vec[0]);
+    SCLogDebug!("parse_kerberos5_request_ticket: realm {:?}", ticket_vec[1]);
+    SCLogDebug!("parse_kerberos5_request_ticket: sname {:?}", ticket_vec[2]);
+    SCLogDebug!("parse_kerberos5_request_ticket: enc-part {:?}", ticket_vec[3]);
+
+// TODO switch to parser_der_genericstring when der-parser 0.5.1 is out
+    let realm = match der_parser::parse_der(ticket_vec[1].content.as_slice().unwrap()) {
+        IResult::Done(_, o) => o,
+        IResult::Incomplete(needed) => {
+            SCLogDebug!("parse_kerberos5_request_ticket: needed {:?}", needed);
+            return IResult::Incomplete(needed);
+        },
+        IResult::Error(err) => {
+            SCLogDebug!("parse_kerberos5_request_ticket: err {:?}", err);
+            return IResult::Error(err);
+        },
+    };
+    SCLogDebug!("parse_kerberos5_request_ticket: realm {:?}", realm);
+
+    if !(realm.class == 0 && realm.structured == 0 && realm.tag == 27) {
+        SCLogDebug!("bad realm data");
+        return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_KRB_FMT_ERR)));
+    }
+    let realm_v = realm.content.as_slice().unwrap().to_vec();
+    SCLogDebug!("parse_kerberos5_request_ticket: realm_v {:?}", realm_v);
+
+    let sname = match der_parser::parse_der_sequence(ticket_vec[2].content.as_slice().unwrap()) {
+        IResult::Done(_, o) => o,
+        IResult::Incomplete(needed) => {
+            SCLogDebug!("parse_kerberos5_request_ticket: needed {:?}", needed);
+            return IResult::Incomplete(needed);
+        },
+        IResult::Error(err) => {
+            SCLogDebug!("parse_kerberos5_request_ticket: err {:?}", err);
+            return IResult::Error(err);
+        },
+    };
+    SCLogDebug!("parse_kerberos5_request_ticket: sname {:?}", sname);
+
+    let sname_vec = sname.as_sequence().unwrap(); // parse_der_sequence is checked
+    SCLogDebug!("parse_kerberos5_request_ticket: sname_vec {:?}", sname_vec);
+
+    if sname_vec.len() != 2 {
+        SCLogDebug!("parse_kerberos5_request_ticket: unexpected format");
+        return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_KRB_FMT_ERR)));
+    }
+    let sname_seq = match der_parser::parse_der_sequence(sname_vec[1].content.as_slice().unwrap()) {
+        IResult::Done(_, o) => o,
+        IResult::Incomplete(needed) => {
+            SCLogDebug!("parse_kerberos5_request_ticket: needed {:?}", needed);
+            return IResult::Incomplete(needed);
+        },
+        IResult::Error(err) => {
+            SCLogDebug!("parse_kerberos5_request_ticket: err {:?}", err);
+            return IResult::Error(err);
+        },
+    };
+    SCLogDebug!("parse_kerberos5_request_ticket: sname_seq {:?}", sname_seq);
+    let snamestr_vec = sname_seq.as_sequence().unwrap(); // parse_der_sequence is checked
+
+    let mut snames : Vec<Vec<u8>> = Vec::new();
+    for o in snamestr_vec {
+        SCLogDebug!("parse_kerberos5_request_ticket: sname o {:?}", o);
+        if o.tag == 27 {
+            let v = o.content.as_slice().unwrap().to_vec();
+            SCLogDebug!("sname {:?}", v);
+            snames.push(v);
+        }
+    }
+
+    let t = Kerberos5Ticket {
+        realm: realm_v,
+        snames: snames,
+    };
+    SCLogDebug!("ticket {:?}", t);
+    IResult::Done(&[],t)
+}
+
+// get SPNEGO
+// get OIDS
+// if OID has KERBEROS get KERBEROS data
+// else if OID has NTLMSSP get NTLMSSP
+// else bruteforce NTLMSSP
+
+fn parse_kerberos5_request(blob: &[u8]) -> IResult<&[u8], Kerberos5Ticket>
+{
+    let blob = match der_parser::parse_der(blob) {
+        IResult::Done(_, b) => {
+            match b.content.as_slice() {
+                Ok(b) => { b },
+                _ => { return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_KRB_FMT_ERR))); },
+            }
+        },
+        IResult::Incomplete(needed) => { return IResult::Incomplete(needed); },
+        IResult::Error(err) => { return IResult::Error(err); },
+    };
+    let (rem, base_o) = match der_parser::parse_der_oid(blob) {
+        IResult::Done(rem, o) => (rem, o),
+        IResult::Incomplete(needed) => { return IResult::Incomplete(needed); },
+        IResult::Error(err) => { return IResult::Error(err); },
+    };
+    SCLogDebug!("parse_kerberos5_request: base_o {:?}", base_o);
+
+    // not DER encoded 2 byte length field
+    let (rem, tok_id) = match nom::le_u16(rem) {
+        IResult::Done(rem, o) => (rem, o),
+        IResult::Incomplete(needed) => { return IResult::Incomplete(needed); },
+        IResult::Error(err) => { return IResult::Error(err); },
+    };
+    SCLogDebug!("parse_kerberos5_request: tok_id {}", tok_id);
+
+    // APPLICATION 14
+    let (rem, base_o) = match der_parser::der_read_element_header(rem) {
+        IResult::Done(rem, o) => (rem, o),
+        IResult::Incomplete(needed) => { return IResult::Incomplete(needed); },
+        IResult::Error(err) => { return IResult::Error(err); },
+    };
+    if !(base_o.class == 1 && base_o.structured == 1 && base_o.tag == 14 && base_o.len == rem.len() as u64) {
+        SCLogDebug!("parse_kerberos5_request_ticket: bad data");
+        return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_KRB_FMT_ERR)));
+    }
+
+    let base_seq = match der_parser::parse_der_sequence(rem) {
+        IResult::Done(_, o) => o,
+        IResult::Incomplete(needed) => { return IResult::Incomplete(needed); },
+        IResult::Error(err) => { return IResult::Error(err); },
+    };
+    SCLogDebug!("parse_kerberos5_request: base_seq {:?}", base_seq);
+
+    if base_seq.as_sequence().unwrap().len() < 4 {
+        SCLogDebug!("parse_kerberos5_request_ticket: bad data");
+        return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_KRB_FMT_ERR)));
+    }
+
+    let pvno_s = match base_seq[0].content.as_slice() {
+        Ok(s) => s,
+        Err(_) => {
+            return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_KRB_FMT_ERR)));
+        },
+    };
+    let pvno = match der_parser::parse_der_integer(pvno_s) {
+        IResult::Done(_, o) => o,
+        IResult::Incomplete(needed) => { return IResult::Incomplete(needed); },
+        IResult::Error(err) => { return IResult::Error(err); },
+    };
+    SCLogDebug!("pvno {:?}", pvno);
+
+    let msg_type_s = match base_seq[1].content.as_slice() {
+        Ok(s) => s,
+        Err(_) => {
+            return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_KRB_FMT_ERR)));
+        },
+    };
+    let msg_type = match der_parser::parse_der_integer(msg_type_s) {
+        IResult::Done(_, o) => o,
+        IResult::Incomplete(needed) => { return IResult::Incomplete(needed); },
+        IResult::Error(err) => { return IResult::Error(err); },
+    };
+    SCLogDebug!("msg_type {:?}", msg_type);
+
+    let padding_s = match base_seq[2].content.as_slice() {
+        Ok(s) => s,
+        Err(_) => {
+            return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_KRB_FMT_ERR)));
+        },
+    };
+    let padding = match der_parser::parse_der_bitstring(padding_s) {
+        IResult::Done(_, o) => o,
+        IResult::Incomplete(needed) => { return IResult::Incomplete(needed); },
+        IResult::Error(err) => { return IResult::Error(err); },
+    };
+    SCLogDebug!("padding {:?}", padding);
+
+    let ticket_s = match base_seq[3].content.as_slice() {
+        Ok(s) => s,
+        Err(_) => {
+            return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_KRB_FMT_ERR)));
+        },
+    };
+    parse_kerberos5_request_ticket(ticket_s)
+}
+
+
+pub const SECBLOB_NOT_SPNEGO :  u32 = 128;
+pub const SECBLOB_KRB_FMT_ERR : u32 = 129;
+
+fn parse_secblob_get_spnego(blob: &[u8]) -> IResult<&[u8], &[u8]>
+{
+    let (rem, base_o) = match der_parser::parse_der(blob) {
+        IResult::Done(rem, o) => (rem, o),
+        IResult::Incomplete(needed) => { return IResult::Incomplete(needed); },
+        IResult::Error(err) => { return IResult::Error(err); },
+    };
+    SCLogDebug!("parse_secblob_get_spnego: base_o {:?}", base_o);
+    let d = match base_o.content.as_slice() {
+        Err(_) => { return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_NOT_SPNEGO))); },
+        Ok(d) => d,
+    };
+    let (next, o) = match der_parser::parse_der_oid(d) {
+        IResult::Done(rem,y) => { (rem,y) },
+        IResult::Incomplete(needed) => { return IResult::Incomplete(needed); },
+        IResult::Error(err) => { return IResult::Error(err); },
+    };
+    SCLogDebug!("parse_secblob_get_spnego: sub_o {:?}", o);
+
+    let oid = match o.content.as_oid() {
+        Ok(oid) => oid,
+        Err(_) => {
+            return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_NOT_SPNEGO)));
+        },
+    };
+    SCLogDebug!("oid {}", oid.to_string());
+
+    match oid.to_string().as_str() {
+        "1.3.6.1.5.5.2" => {
+            SCLogDebug!("SPNEGO {}", oid);
+        },
+        _ => {
+            return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_NOT_SPNEGO)));
+        },
+    }
+
+    SCLogDebug!("parse_secblob_get_spnego: next {:?}", next);
+    SCLogDebug!("parse_secblob_get_spnego: DONE");
+    IResult::Done(rem, next)
+}
+
+fn parse_secblob_spnego_start(blob: &[u8]) -> IResult<&[u8], &[u8]>
+{
+    let (rem, o) = match der_parser::parse_der(blob) {
+        IResult::Done(rem,o) => {
+            SCLogDebug!("o {:?}", o);
+            (rem, o)
+        },
+        IResult::Incomplete(needed) => { return IResult::Incomplete(needed); },
+        IResult::Error(err) => { return IResult::Error(err); },
+    };
+    let d = match o.content.as_slice() {
+        Ok(d) => {
+            SCLogDebug!("d: next data len {}",d.len());
+            d
+        },
+        _ => {
+            return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_NOT_SPNEGO)));
+        },
+    };
+    IResult::Done(rem, d)
+}
+
+fn parse_secblob_spnego(state: &mut SMBState, blob: &[u8])
+{
+    let mut ntlmssp = false;
+    let mut kerberos = false;
+    let mut kticket : Option<Kerberos5Ticket> = None;
+
+    let o = match der_parser::parse_der_sequence(blob) {
+        IResult::Done(_, o) => o,
+        _ => { return; },
+    };
+    for s in o {
+        SCLogDebug!("s {:?}", s);
+
+        let n = match s.content.as_slice() {
+            Ok(s) => s,
+            _ => { continue; },
+        };
+        let o = match der_parser::parse_der(n) {
+            IResult::Done(_,x) => x,
+            _ => { continue; },
+        };
+        SCLogDebug!("o {:?}", o);
+        match o.content {
+            der_parser::DerObjectContent::Sequence(ref seq) => {
+                for se in seq {
+                    SCLogDebug!("SEQ {:?}", se);
+                    match se.content {
+                        der_parser::DerObjectContent::OID(ref oid) => {
+                            SCLogDebug!("OID {:?}", oid);
+                            match oid.to_string().as_str() {
+                                "1.2.840.48018.1.2.2" => { SCLogDebug!("Microsoft Kerberos 5"); },
+                                "1.2.840.113554.1.2.2" => { SCLogDebug!("Kerberos 5"); kerberos = true; },
+                                "1.2.840.113554.1.2.2.1" => { SCLogDebug!("krb5-name"); },
+                                "1.2.840.113554.1.2.2.2" => { SCLogDebug!("krb5-principal"); },
+                                "1.2.840.113554.1.2.2.3" => { SCLogDebug!("krb5-user-to-user-mech"); },
+                                "1.3.6.1.4.1.311.2.2.10" => { SCLogDebug!("NTLMSSP"); ntlmssp = true; },
+                                "1.3.6.1.4.1.311.2.2.30" => { SCLogDebug!("NegoEx"); },
+                                _ => { SCLogNotice!("unexpected OID {:?}", oid); },
+                            }
+                        },
+                        _ => { SCLogNotice!("expected OID, got {:?}", se); },
+                    }
+                }
+            },
+            der_parser::DerObjectContent::OctetString(ref os) => {
+                if kerberos {
+                    match parse_kerberos5_request(os) {
+                        IResult::Done(_, t) => {
+                            kticket = Some(t)
+                        },
+                        _ => { },
+                    }
+                }
+
+                if ntlmssp && kticket == None {
+                    SCLogDebug!("parsing expected NTLMSSP");
+                    parse_ntlmssp_blob(state, os);
+                }
+            },
+            _ => {},
+        }
+    }
+
+    state.krb_ticket = kticket;
+}
+
+#[derive(Debug,PartialEq)]
+pub struct NtlmsspData {
+    pub host: Vec<u8>,
+    pub user: Vec<u8>,
+    pub domain: Vec<u8>,
+}
+
+/// take in blob, search for the header and parse it
+fn parse_ntlmssp_blob(state: &mut SMBState, blob: &[u8])
+{
+    SCLogDebug!("NTLMSSP {:?}", blob);
+    match parse_ntlmssp(blob) {
+        IResult::Done(_, nd) => {
+            SCLogDebug!("NTLMSSP TYPE {}/{} nd {:?}",
+                    nd.msg_type, &ntlmssp_type_string(nd.msg_type), nd);
+            match nd.msg_type {
+                NTLMSSP_NEGOTIATE => {
+                },
+                NTLMSSP_AUTH => {
+                    match parse_ntlm_auth_record(nd.data) {
+                        IResult::Done(_, ad) => {
+                            SCLogDebug!("auth data {:?}", ad);
+                            let mut host = ad.host.to_vec();
+                            host.retain(|&i|i != 0x00);
+                            let mut user = ad.user.to_vec();
+                            user.retain(|&i|i != 0x00);
+                            let mut domain = ad.domain.to_vec();
+                            domain.retain(|&i|i != 0x00);
+
+                            let d = NtlmsspData {
+                                host: host,
+                                user: user,
+                                domain: domain,
+                            };
+                            state.ntlmssp = Some(d);
+                        },
+                        _ => {},
+                    }
+                },
+                _ => {},
+            }
+        },
+        _ => {},
+    }
+}
+
+// if spnego parsing fails try to fall back to ntlmssp
+pub fn parse_secblob(state: &mut SMBState, blob: &[u8])
+{
+    match parse_secblob_get_spnego(blob) {
+        IResult::Done(_, spnego) => {
+            match parse_secblob_spnego_start(spnego) {
+                IResult::Done(_, spnego_start) => {
+                    parse_secblob_spnego(state, spnego_start);
+                },
+                _ => {
+                    parse_ntlmssp_blob(state, blob);
+                },
+            }
+        },
+        _ => {
+            parse_ntlmssp_blob(state, blob);
+        },
+    }
+}
diff --git a/rust/src/smb/dcerpc.rs b/rust/src/smb/dcerpc.rs
new file mode 100644 (file)
index 0000000..d944369
--- /dev/null
@@ -0,0 +1,556 @@
+/* Copyright (C) 2017 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+// written by Victor Julien
+extern crate libc;
+
+use nom::IResult;
+use log::*;
+
+use smb::smb::*;
+use smb::smb2::*;
+use smb::dcerpc_records::*;
+use smb::events::*;
+
+pub const DCERPC_TYPE_REQUEST:              u8 = 0;
+pub const DCERPC_TYPE_PING:                 u8 = 1;
+pub const DCERPC_TYPE_RESPONSE:             u8 = 2;
+pub const DCERPC_TYPE_FAULT:                u8 = 3;
+pub const DCERPC_TYPE_WORKING:              u8 = 4;
+pub const DCERPC_TYPE_NOCALL:               u8 = 5;
+pub const DCERPC_TYPE_REJECT:               u8 = 6;
+pub const DCERPC_TYPE_ACK:                  u8 = 7;
+pub const DCERPC_TYPE_CL_CANCEL:            u8 = 8;
+pub const DCERPC_TYPE_FACK:                 u8 = 9;
+pub const DCERPC_TYPE_CANCEL_ACK:           u8 = 10;
+pub const DCERPC_TYPE_BIND:                 u8 = 11;
+pub const DCERPC_TYPE_BINDACK:              u8 = 12;
+pub const DCERPC_TYPE_BINDNAK:              u8 = 13;
+pub const DCERPC_TYPE_ALTER_CONTEXT:        u8 = 14;
+pub const DCERPC_TYPE_ALTER_CONTEXT_RESP:   u8 = 15;
+pub const DCERPC_TYPE_AUTH3:                u8 = 16;
+pub const DCERPC_TYPE_SHUTDOWN:             u8 = 17;
+pub const DCERPC_TYPE_CO_CANCEL:            u8 = 18;
+pub const DCERPC_TYPE_ORPHANED:             u8 = 19;
+pub const DCERPC_TYPE_RTS:                  u8 = 20;
+
+pub fn dcerpc_type_string(t: u8) -> String {
+    match t {
+        DCERPC_TYPE_REQUEST             => "REQUEST",
+        DCERPC_TYPE_PING                => "PING",
+        DCERPC_TYPE_RESPONSE            => "RESPONSE",
+        DCERPC_TYPE_FAULT               => "FAULT",
+        DCERPC_TYPE_WORKING             => "WORKING",
+        DCERPC_TYPE_NOCALL              => "NOCALL",
+        DCERPC_TYPE_REJECT              => "REJECT",
+        DCERPC_TYPE_ACK                 => "ACK",
+        DCERPC_TYPE_CL_CANCEL           => "CL_CANCEL",
+        DCERPC_TYPE_FACK                => "FACK",
+        DCERPC_TYPE_CANCEL_ACK          => "CANCEL_ACK",
+        DCERPC_TYPE_BIND                => "BIND",
+        DCERPC_TYPE_BINDACK             => "BINDACK",
+        DCERPC_TYPE_BINDNAK             => "BINDNAK",
+        DCERPC_TYPE_ALTER_CONTEXT       => "ALTER_CONTEXT",
+        DCERPC_TYPE_ALTER_CONTEXT_RESP  => "ALTER_CONTEXT_RESP",
+        DCERPC_TYPE_AUTH3               => "AUTH3",
+        DCERPC_TYPE_SHUTDOWN            => "SHUTDOWN",
+        DCERPC_TYPE_CO_CANCEL           => "CO_CANCEL",
+        DCERPC_TYPE_ORPHANED            => "ORPHANED",
+        DCERPC_TYPE_RTS                 => "RTS",
+        _ => { return (t).to_string(); },
+    }.to_string()
+}
+
+impl SMBCommonHdr {
+    /// helper for DCERPC tx tracking. Check if we need
+    /// to use the msg_id/multiplex_id in TX tracking.
+    ///
+    pub fn to_dcerpc(&self, vercmd: &SMBVerCmdStat) -> SMBCommonHdr {
+        // only use the msg id for IOCTL, not for READ/WRITE
+        // as there request/response are different transactions
+        let mut use_msg_id = self.msg_id;
+        match vercmd.get_version() {
+            2 => {
+                let (_, cmd2) = vercmd.get_smb2_cmd();
+                let x = match cmd2 as u16 {
+                    SMB2_COMMAND_READ => { 0 },
+                    SMB2_COMMAND_WRITE => { 0 },
+                    SMB2_COMMAND_IOCTL => { self.msg_id },
+                    _ => { self.msg_id },
+                };
+                use_msg_id = x;
+            },
+            1 => {
+                SCLogDebug!("FIXME TODO");
+                //let (_, cmd1) = vercmd.get_smb1_cmd();
+                //if cmd1 != SMB1_COMMAND_IOCTL {
+                use_msg_id = 0;
+                //}
+            },
+            _ => { },
+        }
+        SMBCommonHdr {
+            ssn_id: self.ssn_id,
+            tree_id: self.tree_id,
+            msg_id: use_msg_id,
+            rec_type: SMBHDR_TYPE_DCERPCTX,
+        }
+    }
+}
+
+#[derive(Debug)]
+pub struct DCERPCIface {
+    pub uuid: Vec<u8>,
+    pub ver: u16,
+    pub ver_min: u16,
+    pub ack_result: u16,
+    pub ack_reason: u16,
+    pub acked: bool,
+}
+
+impl DCERPCIface {
+    pub fn new(uuid: Vec<u8>, ver: u16, ver_min: u16) -> DCERPCIface {
+        DCERPCIface {
+            uuid: uuid,
+            ver:ver,
+            ver_min:ver_min,
+            ack_result:0,
+            ack_reason:0,
+            acked:false,
+        }
+    }
+}
+
+pub fn dcerpc_uuid_to_string(i: &DCERPCIface) -> String {
+    let output = format!("{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
+            i.uuid[0],  i.uuid[1],  i.uuid[2],  i.uuid[3],
+            i.uuid[4],  i.uuid[5],  i.uuid[6],  i.uuid[7],
+            i.uuid[8],  i.uuid[9],  i.uuid[10], i.uuid[11],
+            i.uuid[12], i.uuid[13], i.uuid[14], i.uuid[15]);
+    return output;
+}
+
+
+#[derive(Debug)]
+pub struct SMBTransactionDCERPC {
+    pub opnum: u16,
+    pub req_cmd: u8,
+    pub res_cmd: u8,
+    pub res_set: bool,
+    pub call_id: u32,
+    pub frag_cnt_ts: u16,
+    pub frag_cnt_tc: u16,
+    pub stub_data_ts: Vec<u8>,
+    pub stub_data_tc: Vec<u8>,
+}
+
+impl SMBTransactionDCERPC {
+    pub fn new(req: u8, call_id: u32) -> SMBTransactionDCERPC {
+        return SMBTransactionDCERPC {
+            opnum: 0,
+            req_cmd: req,
+            res_cmd: 0,
+            res_set: false,
+            call_id: call_id,
+            frag_cnt_ts: 0,
+            frag_cnt_tc: 0,
+            stub_data_ts:Vec::new(),
+            stub_data_tc:Vec::new(),
+        }
+    }
+    pub fn set_result(&mut self, res: u8) {
+        self.res_set = true;
+        self.res_cmd = res;
+    }
+}
+
+impl SMBState {
+    pub fn new_dcerpc_tx(&mut self, hdr: SMBCommonHdr, vercmd: SMBVerCmdStat, cmd: u8, call_id: u32)
+        -> (&mut SMBTransaction)
+    {
+        let mut tx = self.new_tx();
+        tx.hdr = hdr;
+        tx.vercmd = vercmd;
+        tx.type_data = Some(SMBTransactionTypeData::DCERPC(
+                    SMBTransactionDCERPC::new(cmd, call_id)));
+
+        SCLogDebug!("SMB: TX DCERPC created: ID {} hdr {:?}", tx.id, tx.hdr);
+        self.transactions.push(tx);
+        let tx_ref = self.transactions.last_mut();
+        return tx_ref.unwrap();
+    }
+
+    pub fn get_dcerpc_tx(&mut self, hdr: &SMBCommonHdr, vercmd: &SMBVerCmdStat, call_id: u32)
+        -> Option<&mut SMBTransaction>
+    {
+        let dce_hdr = hdr.to_dcerpc(vercmd);
+
+        SCLogDebug!("looking for {:?}", dce_hdr);
+        for tx in &mut self.transactions {
+            let found = dce_hdr == tx.hdr.to_dcerpc(vercmd) &&
+                match tx.type_data {
+                Some(SMBTransactionTypeData::DCERPC(ref x)) => {
+                    x.call_id == call_id
+                },
+                _ => { false },
+            };
+            if found {
+                return Some(tx);
+            }
+        }
+        return None;
+    }
+}
+
+/// Handle DCERPC request data from a WRITE, IOCTL or TRANS record.
+/// return bool indicating whether an tx has been created/updated.
+///
+pub fn smb_write_dcerpc_record<'b>(state: &mut SMBState,
+        vercmd: SMBVerCmdStat,
+        hdr: SMBCommonHdr,
+        data: &'b [u8]) -> bool
+{
+    let mut bind_ifaces : Option<Vec<DCERPCIface>> = None;
+
+    SCLogDebug!("called for {} bytes of data", data.len());
+    match parse_dcerpc_record(data) {
+        IResult::Done(_, dcer) => {
+            SCLogDebug!("DCERPC: version {}.{} write data {} => {:?}",
+                    dcer.version_major, dcer.version_minor, dcer.data.len(), dcer);
+
+            /* if this isn't the first frag, simply update the existing
+             * tx with the additional stub data */
+            if dcer.packet_type == DCERPC_TYPE_REQUEST && dcer.first_frag == false {
+                SCLogDebug!("NOT the first frag. Need to find an existing TX");
+                match parse_dcerpc_request_record(dcer.data, dcer.frag_len, dcer.little_endian) {
+                    IResult::Done(_, recr) => {
+                        let found = match state.get_dcerpc_tx(&hdr, &vercmd, dcer.call_id) {
+                            Some(tx) => {
+                                SCLogDebug!("previous CMD {} found at tx {} => {:?}",
+                                        dcer.packet_type, tx.id, tx);
+                                if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data {
+                                    SCLogDebug!("additional frag of size {}", recr.data.len());
+                                    tdn.stub_data_ts.extend_from_slice(&recr.data);
+                                    tdn.frag_cnt_ts += 1;
+                                    SCLogDebug!("stub_data now {}", tdn.stub_data_ts.len());
+                                }
+                                if dcer.last_frag {
+                                    SCLogDebug!("last frag set, so request side of DCERPC closed");
+                                    tx.request_done = true;
+                                } else {
+                                    SCLogDebug!("NOT last frag, so request side of DCERPC remains open");
+                                }
+                                true
+                            },
+                            None => {
+                                SCLogDebug!("NO previous CMD {} found", dcer.packet_type);
+                                false
+                            },
+                        };
+                        return found;
+                    },
+                    _ => {
+                        state.set_event(SMBEvent::MalformedData);
+                        return false;
+                    },
+                }
+            }
+
+            let tx = state.new_dcerpc_tx(hdr, vercmd, dcer.packet_type, dcer.call_id);
+            match dcer.packet_type {
+                DCERPC_TYPE_REQUEST => {
+                    match parse_dcerpc_request_record(dcer.data, dcer.frag_len, dcer.little_endian) {
+                        IResult::Done(_, recr) => {
+                            SCLogDebug!("DCERPC: REQUEST {:?}", recr);
+                            if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data {
+                                SCLogDebug!("first frag size {}", recr.data.len());
+                                tdn.stub_data_ts.extend_from_slice(&recr.data);
+                                tdn.opnum = recr.opnum;
+                                tdn.frag_cnt_ts += 1;
+                                SCLogDebug!("DCERPC: REQUEST opnum {} stub data len {}",
+                                        tdn.opnum, tdn.stub_data_ts.len());
+                            }
+                            if dcer.last_frag {
+                                tx.request_done = true;
+                            } else {
+                                SCLogDebug!("NOT last frag, so request side of DCERPC remains open");
+                            }
+                        },
+                        _ => {
+                            tx.set_event(SMBEvent::MalformedData);
+                        },
+                    }
+                },
+                DCERPC_TYPE_BIND => {
+                    let brec = if dcer.little_endian == true {
+                        parse_dcerpc_bind_record(dcer.data)
+                    } else {
+                        parse_dcerpc_bind_record_big(dcer.data)
+                    };
+                    match brec {
+                        IResult::Done(_, bindr) => {
+                            SCLogDebug!("SMB DCERPC {:?} BIND {:?}", dcer, bindr);
+
+                            if bindr.ifaces.len() > 0 {
+                                let mut ifaces: Vec<DCERPCIface> = Vec::new();
+                                for i in bindr.ifaces {
+                                    let x = if dcer.little_endian == true {
+                                        vec![i.iface[3],  i.iface[2],  i.iface[1],  i.iface[0],
+                                             i.iface[5],  i.iface[4],  i.iface[7],  i.iface[6],
+                                             i.iface[8],  i.iface[9],  i.iface[10], i.iface[11],
+                                             i.iface[12], i.iface[13], i.iface[14], i.iface[15]]
+                                    } else {
+                                        i.iface.to_vec()
+                                    };
+                                    let d = DCERPCIface::new(x,i.ver,i.ver_min);
+                                    SCLogDebug!("UUID {} version {}/{} bytes {:?}",
+                                            dcerpc_uuid_to_string(&d),
+                                            i.ver, i.ver_min,i.iface);
+                                    ifaces.push(d);
+                                }
+                                bind_ifaces = Some(ifaces);
+                            }
+                            tx.request_done = true;
+                        },
+                        _ => {
+                            tx.set_event(SMBEvent::MalformedData);
+                        },
+                    }
+                }
+                21...255 => {
+                    tx.set_event(SMBEvent::MalformedData);
+                },
+                _ => { }, // valid type w/o special processing
+            }
+        },
+        _ => {
+            state.set_event(SMBEvent::MalformedData);
+        },
+    }
+
+    state.dcerpc_ifaces = bind_ifaces; // TODO store per ssn
+    return true;
+}
+
+/// Update TX for bind ack. Needs to update both tx and state.
+///
+fn smb_dcerpc_response_bindack(
+        state: &mut SMBState,
+        vercmd: SMBVerCmdStat,
+        hdr: SMBCommonHdr,
+        dcer: &DceRpcRecord,
+        ntstatus: u32)
+{
+    match parse_dcerpc_bindack_record(dcer.data) {
+        IResult::Done(_, bindackr) => {
+            SCLogDebug!("SMB READ BINDACK {:?}", bindackr);
+
+            let found = match state.get_dcerpc_tx(&hdr, &vercmd, dcer.call_id) {
+                Some(tx) => {
+                    if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data {
+                        tdn.set_result(DCERPC_TYPE_BINDACK);
+                    }
+                    tx.vercmd.set_ntstatus(ntstatus);
+                    tx.response_done = true;
+                    true
+                },
+                None => false,
+            };
+            if found {
+                match state.dcerpc_ifaces {
+                    Some(ref mut ifaces) => {
+                        let mut i = 0;
+                        for r in bindackr.results {
+                            if i >= ifaces.len() {
+                                // TODO set event: more acks that requests
+                                break;
+                            }
+                            ifaces[i].ack_result = r.ack_result;
+                            ifaces[i].acked = true;
+                            i = i + 1;
+                        }
+                    },
+                    _ => {},
+                }
+            }
+        },
+        _ => {
+            state.set_event(SMBEvent::MalformedData);
+        },
+    }
+}
+
+fn smb_read_dcerpc_record_error(state: &mut SMBState,
+        hdr: SMBCommonHdr, vercmd: SMBVerCmdStat, ntstatus: u32)
+    -> bool
+{
+    let ver = vercmd.get_version();
+    let cmd = if ver == 2 {
+        let (_, c) = vercmd.get_smb2_cmd();
+        c
+    } else {
+        let (_, c) = vercmd.get_smb1_cmd();
+        c as u16
+    };
+
+    let found = match state.get_generic_tx(ver, cmd, &hdr) {
+        Some(tx) => {
+            SCLogDebug!("found");
+            tx.set_status(ntstatus, false);
+            tx.response_done = true;
+            true
+        },
+        None => {
+            SCLogNotice!("NOT found");
+            false
+        },
+    };
+    return found;
+}
+
+/// Handle DCERPC reply record. Called for READ, TRANS, IOCTL
+///
+pub fn smb_read_dcerpc_record<'b>(state: &mut SMBState,
+        vercmd: SMBVerCmdStat,
+        hdr: SMBCommonHdr,
+        guid: &[u8],
+        indata: &'b [u8]) -> bool
+{
+    let (_, ntstatus) = vercmd.get_ntstatus();
+
+    if ntstatus != SMB_NTSTATUS_SUCCESS {
+        return smb_read_dcerpc_record_error(state, hdr, vercmd, ntstatus);
+    }
+
+    SCLogDebug!("lets first see if we have prior data");
+    // msg_id 0 as this data crosses cmd/reply pairs
+    let ehdr = SMBCommonHdr::new(SMBHDR_TYPE_TRANS_FRAG,
+            hdr.ssn_id as u64, hdr.tree_id as u32, 0 as u64);
+    let ekey = SMBHashKeyHdrGuid::new(ehdr, guid.to_vec());
+    SCLogDebug!("ekey {:?}", ekey);
+    let mut prevdata = match state.ssnguid2vec_map.remove(&ekey) {
+        Some(s) => s,
+            None => Vec::new(),
+    };
+    SCLogDebug!("indata {} prevdata {}", indata.len(), prevdata.len());
+    prevdata.extend_from_slice(&indata);
+    let data = prevdata;
+
+    let mut malformed = false;
+
+    if data.len() == 0 {
+        SCLogNotice!("weird: no DCERPC data"); // TODO
+        // TODO set event?
+        return false;
+
+    } else {
+        match parse_dcerpc_record(&data) {
+            IResult::Done(_, dcer) => {
+                SCLogDebug!("DCERPC: version {}.{} read data {} => {:?}",
+                        dcer.version_major, dcer.version_minor, dcer.data.len(), dcer);
+
+                if ntstatus == SMB_NTSTATUS_BUFFER_OVERFLOW && data.len() < dcer.frag_len as usize {
+                    SCLogDebug!("short record {} < {}: lets see if we expected this", data.len(), dcer.frag_len);
+                    let ehdr = SMBCommonHdr::new(SMBHDR_TYPE_MAX_SIZE,
+                            hdr.ssn_id as u64, hdr.tree_id as u32, hdr.msg_id as u64);
+                    let max_size = match state.ssn2maxsize_map.remove(&ehdr) {
+                        Some(s) => s,
+                        None => 0,
+                    };
+                    if max_size as usize == data.len() {
+                        SCLogDebug!("YES this is expected. We should now see a follow up READ for {} bytes",
+                                dcer.frag_len as usize - data.len());
+                        let store = match state.get_dcerpc_tx(&hdr, &vercmd, dcer.call_id) {
+                            Some(_) => true,
+                            None => {
+                                SCLogDebug!("no TX found");
+                                false
+                            },
+                        };
+                        if store {
+                            SCLogDebug!("storing partial data in state");
+                            let ehdr = SMBCommonHdr::new(SMBHDR_TYPE_MAX_SIZE,
+                                    hdr.ssn_id as u64, hdr.tree_id as u32, 0 as u64);
+                            state.ssn2vec_map.insert(ehdr, data.to_vec());
+                        }
+                        return true; // TODO review
+                    }
+                }
+
+                if dcer.packet_type == DCERPC_TYPE_BINDACK {
+                    smb_dcerpc_response_bindack(state, vercmd, hdr, &dcer, ntstatus);
+                    return true;
+                }
+
+                let tx = match state.get_dcerpc_tx(&hdr, &vercmd, dcer.call_id) {
+                    Some(tx) => tx,
+                    None => {
+                        SCLogNotice!("no tx");
+                        return false; },
+                };
+
+                match dcer.packet_type {
+                    DCERPC_TYPE_RESPONSE => {
+                        match parse_dcerpc_response_record(dcer.data, dcer.frag_len) {
+                            IResult::Done(_, respr) => {
+                                SCLogDebug!("SMBv1 READ RESPONSE {:?}", respr);
+                                if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data {
+                                    SCLogDebug!("CMD 11 found at tx {}", tx.id);
+                                    tdn.set_result(DCERPC_TYPE_RESPONSE);
+                                    tdn.stub_data_tc.extend_from_slice(&respr.data);
+                                    tdn.frag_cnt_tc += 1;
+                                }
+                                tx.vercmd.set_ntstatus(ntstatus);
+                                tx.response_done = dcer.last_frag;
+                            },
+                            _ => {
+                                tx.set_event(SMBEvent::MalformedData);
+                            },
+                        }
+                    },
+                    DCERPC_TYPE_BINDACK => {
+                        // handled above
+                    },
+                    21...255 => {
+                        if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data {
+                            tdn.set_result(dcer.packet_type);
+                        }
+                        tx.vercmd.set_ntstatus(ntstatus);
+                        tx.response_done = true;
+                        tx.set_event(SMBEvent::MalformedData);
+                    }
+                    _ => { // valid type w/o special processing
+                        if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data {
+                            tdn.set_result(dcer.packet_type);
+                        }
+                        tx.vercmd.set_ntstatus(ntstatus);
+                        tx.response_done = true;
+                    },
+                }
+            },
+            _ => {
+                malformed = true;
+            },
+        }
+    }
+
+    if malformed {
+        state.set_event(SMBEvent::MalformedData);
+    }
+
+    return true;
+}
diff --git a/rust/src/smb/dcerpc_records.rs b/rust/src/smb/dcerpc_records.rs
new file mode 100644 (file)
index 0000000..b4ed08f
--- /dev/null
@@ -0,0 +1,233 @@
+/* Copyright (C) 2017 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use nom::{rest, le_u8, be_u16, le_u16, le_u32, IResult, ErrorKind, Endianness};
+
+#[derive(Debug,PartialEq)]
+pub struct DceRpcResponseRecord<'a> {
+    pub data: &'a[u8],
+}
+
+/// parse a packet type 'response' DCERPC record. Implemented
+/// as function to be able to pass the fraglen in.
+pub fn parse_dcerpc_response_record(i:&[u8], frag_len: u16 )
+    -> IResult<&[u8], DceRpcResponseRecord>
+{
+    if frag_len < 24 {
+        return IResult::Error(error_code!(ErrorKind::Custom(128)));
+    }
+    do_parse!(i,
+                take!(8)
+            >>  data:take!(frag_len - 24)
+            >> (DceRpcResponseRecord {
+                    data:data,
+               })
+    )
+}
+
+#[derive(Debug,PartialEq)]
+pub struct DceRpcRequestRecord<'a> {
+    pub opnum: u16,
+    pub data: &'a[u8],
+}
+
+/// parse a packet type 'request' DCERPC record. Implemented
+/// as function to be able to pass the fraglen in.
+pub fn parse_dcerpc_request_record(i:&[u8], frag_len: u16, little: bool)
+    -> IResult<&[u8], DceRpcRequestRecord>
+{
+    if frag_len < 24 {
+        return IResult::Error(error_code!(ErrorKind::Custom(128)));
+    }
+    do_parse!(i,
+                take!(6)
+            >>  endian: value!(if little { Endianness::Little } else { Endianness::Big })
+            >>  opnum: u16!(endian)
+            >>  data:take!(frag_len - 24)
+            >> (DceRpcRequestRecord {
+                    opnum:opnum,
+                    data:data,
+               })
+    )
+}
+
+#[derive(Debug,PartialEq)]
+pub struct DceRpcBindIface<'a> {
+    pub iface: &'a[u8],
+    pub ver: u16,
+    pub ver_min: u16,
+}
+
+named!(pub parse_dcerpc_bind_iface<DceRpcBindIface>,
+    do_parse!(
+            ctx_id: le_u16
+        >>  num_trans_items: le_u8
+        >>  take!(1) // reserved
+        >>  interface: take!(16)
+        >>  ver: le_u16
+        >>  ver_min: le_u16
+        >>  take!(20)
+        >> (DceRpcBindIface {
+                iface:interface,
+                ver:ver,
+                ver_min:ver_min,
+            })
+));
+
+named!(pub parse_dcerpc_bind_iface_big<DceRpcBindIface>,
+    do_parse!(
+            ctx_id: le_u16
+        >>  num_trans_items: le_u8
+        >>  take!(1) // reserved
+        >>  interface: take!(16)
+        >>  ver_min: be_u16
+        >>  ver: be_u16
+        >>  take!(20)
+        >> (DceRpcBindIface {
+                iface:interface,
+                ver:ver,
+                ver_min:ver_min,
+            })
+));
+
+#[derive(Debug,PartialEq)]
+pub struct DceRpcBindRecord<'a> {
+    pub num_ctx_items: u8,
+    pub ifaces: Vec<DceRpcBindIface<'a>>,
+}
+
+named!(pub parse_dcerpc_bind_record<DceRpcBindRecord>,
+    do_parse!(
+            max_xmit_frag: le_u16
+        >>  max_recv_frag: le_u16
+        >>  assoc_group: take!(4)
+        >>  num_ctx_items: le_u8
+        >>  take!(3) // reserved
+        >>  ifaces: count!(parse_dcerpc_bind_iface, num_ctx_items as usize)
+        >> (DceRpcBindRecord {
+                num_ctx_items:num_ctx_items,
+                ifaces:ifaces,
+           })
+));
+
+named!(pub parse_dcerpc_bind_record_big<DceRpcBindRecord>,
+    do_parse!(
+            max_xmit_frag: be_u16
+        >>  max_recv_frag: be_u16
+        >>  assoc_group: take!(4)
+        >>  num_ctx_items: le_u8
+        >>  take!(3) // reserved
+        >>  ifaces: count!(parse_dcerpc_bind_iface_big, num_ctx_items as usize)
+        >> (DceRpcBindRecord {
+                num_ctx_items:num_ctx_items,
+                ifaces:ifaces,
+           })
+));
+
+#[derive(Debug,PartialEq)]
+pub struct DceRpcBindAckResult<'a> {
+    pub ack_result: u16,
+    pub ack_reason: u16,
+    pub transfer_syntax: &'a[u8],
+    pub syntax_version: u32,
+}
+
+named!(pub parse_dcerpc_bindack_result<DceRpcBindAckResult>,
+    do_parse!(
+            ack_result: le_u16
+        >>  ack_reason: le_u16
+        >>  transfer_syntax: take!(16)
+        >>  syntax_version: le_u32
+        >> (DceRpcBindAckResult {
+                ack_result:ack_result,
+                ack_reason:ack_reason,
+                transfer_syntax:transfer_syntax,
+                syntax_version:syntax_version,
+            })
+));
+
+#[derive(Debug,PartialEq)]
+pub struct DceRpcBindAckRecord<'a> {
+    pub num_results: u8,
+    pub results: Vec<DceRpcBindAckResult<'a>>,
+}
+
+named!(pub parse_dcerpc_bindack_record<DceRpcBindAckRecord>,
+    do_parse!(
+            max_xmit_frag: le_u16
+        >>  max_recv_frag: le_u16
+        >>  assoc_group: take!(4)
+        >>  sec_addr_len: le_u16
+        >>  take!(sec_addr_len)
+        >>  cond!((sec_addr_len+2) % 4 != 0, take!(4 - (sec_addr_len+2) % 4))
+        >>  num_results: le_u8
+        >>  take!(3) // padding
+        >>  results: count!(parse_dcerpc_bindack_result, num_results as usize)
+        >> (DceRpcBindAckRecord {
+                num_results:num_results,
+                results:results,
+           })
+));
+
+#[derive(Debug,PartialEq)]
+pub struct DceRpcRecord<'a> {
+    pub version_major: u8,
+    pub version_minor: u8,
+
+    pub first_frag: bool,
+    pub last_frag: bool,
+
+    pub frag_len: u16,
+
+    pub little_endian: bool,
+
+    pub packet_type: u8,
+
+    pub call_id: u32,
+    pub data: &'a[u8],
+}
+
+named!(pub parse_dcerpc_record<DceRpcRecord>,
+    do_parse!(
+            version_major: le_u8
+        >>  version_minor: le_u8
+        >>  packet_type: le_u8
+        >>  packet_flags: bits!(tuple!(
+               take_bits!(u8, 6),
+               take_bits!(u8, 1),   // last (1)
+               take_bits!(u8, 1)))  // first (2)
+        >>  data_rep: bits!(tuple!(
+                take_bits!(u32, 3),
+                take_bits!(u32, 1),     // endianess
+                take_bits!(u32, 28)))
+        >>  endian: value!(if data_rep.1 == 0 { Endianness::Big } else { Endianness::Little })
+        >>  frag_len: u16!(endian)
+        >>  auth: u16!(endian)
+        >>  call_id: u32!(endian)
+        >>  data:rest
+        >> (DceRpcRecord {
+                version_major:version_major,
+                version_minor:version_minor,
+                packet_type:packet_type,
+                first_frag:packet_flags.2==1,
+                last_frag:packet_flags.1==1,
+                frag_len: frag_len,
+                little_endian:data_rep.1==1,
+                call_id:call_id,
+                data:data,
+           })
+));
diff --git a/rust/src/smb/debug.rs b/rust/src/smb/debug.rs
new file mode 100644 (file)
index 0000000..f231e6d
--- /dev/null
@@ -0,0 +1,65 @@
+/* Copyright (C) 2018 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use log::*;
+use smb::smb::*;
+
+impl SMBState {
+    pub fn _debug_tx_stats(&self) {
+        if self.transactions.len() > 1 {
+            let txf = self.transactions.first().unwrap();
+            let txl = self.transactions.last().unwrap();
+
+            SCLogNotice!("TXs {} MIN {} MAX {}", self.transactions.len(), txf.id, txl.id);
+            SCLogNotice!("- OLD tx.id {}: {:?}", txf.id, txf);
+            SCLogNotice!("- NEW tx.id {}: {:?}", txl.id, txl);
+            self._dump_txs();
+        }
+    }
+
+    pub fn _dump_txs(&self) {
+        let len = self.transactions.len();
+        for i in 0..len {
+            let tx = &self.transactions[i];
+            let ver = tx.vercmd.get_version();
+            let _smbcmd;
+            if ver == 2 {
+                let (_, cmd) = tx.vercmd.get_smb2_cmd();
+                _smbcmd = cmd;
+            } else {
+                let (_, cmd) = tx.vercmd.get_smb1_cmd();
+                _smbcmd = cmd as u16;
+            }
+
+            match tx.type_data {
+                Some(SMBTransactionTypeData::FILE(ref d)) => {
+                    SCLogDebug!("idx {} tx id {} progress {}/{} filename {} type_data {:?}",
+                            i, tx.id, tx.request_done, tx.response_done,
+                            String::from_utf8_lossy(&d.file_name), tx.type_data);
+                },
+                _ => {
+                    SCLogNotice!("idx {} tx id {} ver:{} cmd:{} progress {}/{} type_data {:?} tx {:?}",
+                            i, tx.id, ver, _smbcmd, tx.request_done, tx.response_done, tx.type_data, tx);
+                },
+            }
+        }
+    }
+
+    pub fn _debug_state_stats(&self) {
+        SCLogNotice!("ssn2vec_map {} guid2name_map {} ssn2vecoffset_map {} ssn2tree_map {} ssn2maxsize_map {} ssnguid2vec_map {} tcp_buffer_ts {} tcp_buffer_tc {} file_ts_guid {} file_tc_guid {} transactions {}", self.ssn2vec_map.len(), self.guid2name_map.len(), self.ssn2vecoffset_map.len(), self.ssn2tree_map.len(), self.ssn2maxsize_map.len(), self.ssnguid2vec_map.len(), self.tcp_buffer_ts.len(), self.tcp_buffer_tc.len(), self.file_ts_guid.len(), self.file_tc_guid.len(), self.transactions.len());
+    }
+}
diff --git a/rust/src/smb/detect.rs b/rust/src/smb/detect.rs
new file mode 100644 (file)
index 0000000..698e2c7
--- /dev/null
@@ -0,0 +1,214 @@
+/* Copyright (C) 2017 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+extern crate libc;
+
+use std;
+use std::ptr;
+use core::*;
+use log::*;
+use smb::smb::*;
+
+#[no_mangle]
+pub extern "C" fn rs_smb_tx_get_share(tx: &mut SMBTransaction,
+                                            buffer: *mut *const libc::uint8_t,
+                                            buffer_len: *mut libc::uint32_t)
+                                            -> libc::uint8_t
+{
+    match tx.type_data {
+        Some(SMBTransactionTypeData::TREECONNECT(ref x)) => {
+            SCLogDebug!("is_pipe {}", x.is_pipe);
+            if !x.is_pipe {
+                unsafe {
+                    *buffer = x.share_name.as_ptr();
+                    *buffer_len = x.share_name.len() as libc::uint32_t;
+                    return 1;
+                }
+            }
+        }
+        _ => {
+        }
+    }
+
+    unsafe {
+        *buffer = ptr::null();
+        *buffer_len = 0;
+    }
+    return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_smb_tx_get_named_pipe(tx: &mut SMBTransaction,
+                                            buffer: *mut *const libc::uint8_t,
+                                            buffer_len: *mut libc::uint32_t)
+                                            -> libc::uint8_t
+{
+    match tx.type_data {
+        Some(SMBTransactionTypeData::TREECONNECT(ref x)) => {
+            SCLogDebug!("is_pipe {}", x.is_pipe);
+            if x.is_pipe {
+                unsafe {
+                    *buffer = x.share_name.as_ptr();
+                    *buffer_len = x.share_name.len() as libc::uint32_t;
+                    return 1;
+                }
+            }
+        }
+        _ => {
+        }
+    }
+
+    unsafe {
+        *buffer = ptr::null();
+        *buffer_len = 0;
+    }
+    return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_smb_tx_get_stub_data(tx: &mut SMBTransaction,
+                                            direction: u8,
+                                            buffer: *mut *const libc::uint8_t,
+                                            buffer_len: *mut libc::uint32_t)
+                                            -> libc::uint8_t
+{
+    match tx.type_data {
+        Some(SMBTransactionTypeData::DCERPC(ref x)) => {
+            let vref = if direction == STREAM_TOSERVER {
+                &x.stub_data_ts
+            } else {
+                &x.stub_data_tc
+            };
+            if vref.len() > 0 {
+                unsafe {
+                    *buffer = vref.as_ptr();
+                    *buffer_len = vref.len() as libc::uint32_t;
+                    return 1;
+                }
+            }
+        }
+        _ => {
+        }
+    }
+
+    unsafe {
+        *buffer = ptr::null();
+        *buffer_len = 0;
+    }
+    return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_smb_tx_get_dce_opnum(tx: &mut SMBTransaction,
+                                            opnum: *mut libc::uint16_t)
+                                            -> libc::uint8_t
+{
+    SCLogNotice!("rs_smb_tx_get_dce_opnum: start");
+    match tx.type_data {
+        Some(SMBTransactionTypeData::DCERPC(ref x)) => {
+            if x.req_cmd == 1 { // REQUEST
+                unsafe {
+                    *opnum = x.opnum as libc::uint16_t;
+                    return 1;
+                }
+            }
+        }
+        _ => {
+        }
+    }
+
+    unsafe {
+        *opnum = 0;
+    }
+    return 0;
+}
+
+/* based on:
+ * typedef enum DetectDceIfaceOperators_ {
+ *    DETECT_DCE_IFACE_OP_NONE = 0,
+ *    DETECT_DCE_IFACE_OP_LT,
+ *    DETECT_DCE_IFACE_OP_GT,
+ *    DETECT_DCE_IFACE_OP_EQ,
+ *    DETECT_DCE_IFACE_OP_NE,
+ * } DetectDceIfaceOperators;
+ */
+#[inline]
+fn match_version(op: u8, them: u16, us: u16) -> bool {
+    let result = match op {
+        0 => { // NONE
+            true
+        },
+        1 => { // LT
+            (them < us)
+        },
+        2 => { // GT
+            (them > us)
+        },
+        3 => { // EQ
+            (them == us)
+        },
+        4 => { // NE
+            (them != us)
+        },
+        _ => {
+            panic!("called with invalid op {}", op);
+        },
+    };
+    result
+}
+
+/* mimic logic that is/was in the C code:
+ * - match on REQUEST (so not on BIND/BINDACK (probably for mixing with
+ *                     dce_opnum and dce_stub_data)
+ * - only match on approved ifaces (so ack_result == 0) */
+#[no_mangle]
+pub extern "C" fn rs_smb_tx_get_dce_iface(state: &mut SMBState,
+                                            tx: &mut SMBTransaction,
+                                            uuid_ptr: *mut libc::uint8_t,
+                                            uuid_len: libc::uint16_t,
+                                            ver_op: libc::uint8_t,
+                                            ver_check: libc::uint16_t)
+                                            -> libc::uint8_t
+{
+    let is_dcerpc_request = match tx.type_data {
+        Some(SMBTransactionTypeData::DCERPC(ref x)) => { x.req_cmd == 1 },
+        _ => { false },
+    };
+    if !is_dcerpc_request {
+        return 0;
+    }
+    let ifaces = match state.dcerpc_ifaces {
+        Some(ref x) => x,
+        _ => {
+            return 0;
+        },
+    };
+
+    let uuid = unsafe{std::slice::from_raw_parts(uuid_ptr, uuid_len as usize)};
+    SCLogDebug!("looking for UUID {:?}", uuid);
+
+    for i in ifaces {
+        SCLogDebug!("stored UUID {:?} acked {} ack_result {}", i, i.acked, i.ack_result);
+
+        if i.acked && i.ack_result == 0 && i.uuid == uuid {
+            if match_version(ver_op as u8, ver_check as u16, i.ver) {
+                return 1;
+            }
+        }
+    }
+    return 0;
+}
diff --git a/rust/src/smb/events.rs b/rust/src/smb/events.rs
new file mode 100644 (file)
index 0000000..8e1f56f
--- /dev/null
@@ -0,0 +1,71 @@
+/* Copyright (C) 2018 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use core::*;
+use log::*;
+use smb::smb::*;
+
+#[repr(u32)]
+pub enum SMBEvent {
+    InternalError = 0,
+    MalformedData = 1,
+    RecordOverflow = 2,
+    MalformedNtlmsspRequest = 3,
+    MalformedNtlmsspResponse = 4,
+    DuplicateNegotiate = 5,
+}
+
+pub fn smb_str_to_event(instr: &str) -> i32 {
+    SCLogDebug!("checking {}", instr);
+    match instr {
+        "internal_error"                => SMBEvent::InternalError as i32,
+        "malformed_data"                => SMBEvent::MalformedData as i32,
+        "record_overflow"               => SMBEvent::RecordOverflow as i32,
+        "malformed_ntlmssp_request"     => SMBEvent::MalformedNtlmsspRequest as i32,
+        "malformed_ntlmssp_response"    => SMBEvent::MalformedNtlmsspResponse as i32,
+        "duplicate_negotiate"           => SMBEvent::DuplicateNegotiate as i32,
+        _ => -1,
+    }
+}
+
+impl SMBTransaction {
+    /// Set event.
+    pub fn set_event(&mut self, e: SMBEvent) {
+        sc_app_layer_decoder_events_set_event_raw(&mut self.events, e as u8);
+    }
+
+    /// Set events from vector of events.
+    pub fn set_events(&mut self, events: Vec<SMBEvent>) {
+        for e in events {
+            sc_app_layer_decoder_events_set_event_raw(&mut self.events, e as u8);
+        }
+    }
+}
+
+impl SMBState {
+    /// Set an event. The event is set on the most recent transaction.
+    pub fn set_event(&mut self, event: SMBEvent) {
+        let len = self.transactions.len();
+        if len == 0 {
+            return;
+        }
+
+        let tx = &mut self.transactions[len - 1];
+        tx.set_event(event);
+        //sc_app_layer_decoder_events_set_event_raw(&mut tx.events, event as u8);
+    }
+}
diff --git a/rust/src/smb/files.rs b/rust/src/smb/files.rs
new file mode 100644 (file)
index 0000000..7064ab2
--- /dev/null
@@ -0,0 +1,233 @@
+/* Copyright (C) 2018 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use core::*;
+use log::*;
+use filetracker::*;
+use filecontainer::*;
+
+use smb::smb::*;
+
+/// File tracking transaction. Single direction only.
+#[derive(Debug)]
+pub struct SMBTransactionFile {
+    pub direction: u8,
+    pub guid: Vec<u8>,
+    pub file_name: Vec<u8>,
+    pub share_name: Vec<u8>,
+    pub file_tracker: FileTransferTracker,
+}
+
+impl SMBTransactionFile {
+    pub fn new() -> SMBTransactionFile {
+        return SMBTransactionFile {
+            direction: 0,
+            guid: Vec::new(),
+            file_name: Vec::new(),
+            share_name: Vec::new(),
+            file_tracker: FileTransferTracker::new(),
+        }
+    }
+}
+
+/// Wrapper around Suricata's internal file container logic.
+#[derive(Debug)]
+pub struct SMBFiles {
+    pub files_ts: FileContainer,
+    pub files_tc: FileContainer,
+    pub flags_ts: u16,
+    pub flags_tc: u16,
+}
+
+impl SMBFiles {
+    pub fn new() -> SMBFiles {
+        SMBFiles {
+            files_ts:FileContainer::default(),
+            files_tc:FileContainer::default(),
+            flags_ts:0,
+            flags_tc:0,
+        }
+    }
+    pub fn free(&mut self) {
+        self.files_ts.free();
+        self.files_tc.free();
+    }
+
+    pub fn get(&mut self, direction: u8) -> (&mut FileContainer, u16)
+    {
+        if direction == STREAM_TOSERVER {
+            (&mut self.files_ts, self.flags_ts)
+        } else {
+            (&mut self.files_tc, self.flags_tc)
+        }
+    }
+}
+
+/// little wrapper around the FileTransferTracker::new_chunk method
+pub fn filetracker_newchunk(ft: &mut FileTransferTracker, files: &mut FileContainer,
+        flags: u16, name: &Vec<u8>, data: &[u8],
+        chunk_offset: u64, chunk_size: u32, fill_bytes: u8, is_last: bool, xid: &u32)
+{
+    match unsafe {SURICATA_SMB_FILE_CONFIG} {
+        Some(sfcm) => {
+            ft.new_chunk(sfcm, files, flags, &name, data, chunk_offset,
+                    chunk_size, fill_bytes, is_last, xid); }
+        None => panic!("BUG"),
+    }
+}
+
+impl SMBState {
+    pub fn new_file_tx(&mut self, file_guid: &Vec<u8>, file_name: &Vec<u8>, direction: u8)
+        -> (&mut SMBTransaction, &mut FileContainer, u16)
+    {
+        let mut tx = self.new_tx();
+        tx.type_data = Some(SMBTransactionTypeData::FILE(SMBTransactionFile::new()));
+        match tx.type_data {
+            Some(SMBTransactionTypeData::FILE(ref mut d)) => {
+                d.direction = direction;
+                d.guid = file_guid.to_vec();
+                d.file_name = file_name.to_vec();
+                d.file_tracker.tx_id = tx.id - 1;
+            },
+            _ => { },
+        }
+        SCLogDebug!("SMB: new_file_tx: TX FILE created: ID {} NAME {}",
+                tx.id, String::from_utf8_lossy(file_name));
+        self.transactions.push(tx);
+        let tx_ref = self.transactions.last_mut();
+        let (files, flags) = self.files.get(direction);
+        return (tx_ref.unwrap(), files, flags)
+    }
+
+    pub fn get_file_tx_by_guid(&mut self, guid: &Vec<u8>, direction: u8)
+        -> Option<(&mut SMBTransaction, &mut FileContainer, u16)>
+    {
+        let g = guid.to_vec();
+        for tx in &mut self.transactions {
+            let found = match tx.type_data {
+                Some(SMBTransactionTypeData::FILE(ref mut d)) => {
+                    direction == d.direction && g == d.guid
+                },
+                _ => { false },
+            };
+
+            if found {
+                SCLogDebug!("SMB: Found SMB file TX with ID {}", tx.id);
+                let (files, flags) = self.files.get(direction);
+                return Some((tx, files, flags));
+            }
+        }
+        SCLogDebug!("SMB: Failed to find SMB TX with GUID {:?}", guid);
+        return None;
+    }
+
+    fn getfiles(&mut self, direction: u8) -> * mut FileContainer {
+        //SCLogDebug!("direction: {}", direction);
+        if direction == STREAM_TOCLIENT {
+            &mut self.files.files_tc as *mut FileContainer
+        } else {
+            &mut self.files.files_ts as *mut FileContainer
+        }
+    }
+    fn setfileflags(&mut self, direction: u8, flags: u16) {
+        SCLogDebug!("direction: {}, flags: {}", direction, flags);
+        if direction == 1 {
+            self.files.flags_tc = flags;
+        } else {
+            self.files.flags_ts = flags;
+        }
+    }
+
+    // update in progress chunks for file transfers
+    // return how much data we consumed
+    pub fn filetracker_update(&mut self, direction: u8, data: &[u8], gap_size: u32) -> u32 {
+        let mut chunk_left = if direction == STREAM_TOSERVER {
+            self.file_ts_left
+        } else {
+            self.file_tc_left
+        };
+        if chunk_left == 0 {
+            return 0
+        }
+        SCLogDebug!("chunk_left {} data {}", chunk_left, data.len());
+        let file_handle = if direction == STREAM_TOSERVER {
+            self.file_ts_guid.to_vec()
+        } else {
+            self.file_tc_guid.to_vec()
+        };
+
+        let data_to_handle_len = if chunk_left as usize >= data.len() {
+            data.len()
+        } else {
+            chunk_left as usize
+        };
+
+        if chunk_left <= data.len() as u32 {
+            chunk_left = 0;
+        } else {
+            chunk_left -= data.len() as u32;
+        }
+
+        if direction == STREAM_TOSERVER {
+            self.file_ts_left = chunk_left;
+        } else {
+            self.file_tc_left = chunk_left;
+        }
+
+        let ssn_gap = self.ts_ssn_gap | self.tc_ssn_gap;
+        // get the tx and update it
+        let consumed = match self.get_file_tx_by_guid(&file_handle, direction) {
+            Some((tx, files, flags)) => {
+                if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+                    if ssn_gap {
+                        let queued_data = tdf.file_tracker.get_queued_size();
+                        if queued_data > 2000000 { // TODO should probably be configurable
+                            SCLogDebug!("QUEUED size {} while we've seen GAPs. Truncating file.", queued_data);
+                            tdf.file_tracker.trunc(files, flags);
+                        }
+                    }
+
+                    let file_data = &data[0..data_to_handle_len];
+                    let cs = tdf.file_tracker.update(files, flags, file_data, gap_size);
+                    cs
+                } else {
+                    0
+                }
+            },
+            None => {
+                SCLogNotice!("not found for handle {:?}", file_handle);
+                0 },
+        };
+
+        return consumed;
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_smb_getfiles(direction: u8, ptr: *mut SMBState) -> * mut FileContainer {
+    if ptr.is_null() { panic!("NULL ptr"); };
+    let parser = unsafe { &mut *ptr };
+    parser.getfiles(direction)
+}
+
+#[no_mangle]
+pub extern "C" fn rs_smb_setfileflags(direction: u8, ptr: *mut SMBState, flags: u16) {
+    if ptr.is_null() { panic!("NULL ptr"); };
+    let parser = unsafe { &mut *ptr };
+    SCLogDebug!("direction {} flags {}", direction, flags);
+    parser.setfileflags(direction, flags)
+}
diff --git a/rust/src/smb/log.rs b/rust/src/smb/log.rs
new file mode 100644 (file)
index 0000000..c22accc
--- /dev/null
@@ -0,0 +1,275 @@
+/* Copyright (C) 2017 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+extern crate libc;
+
+use std::str;
+use std::string::String;
+use json::*;
+use smb::smb::*;
+use smb::smb1::*;
+use smb::smb2::*;
+use smb::dcerpc::*;
+use nom;
+
+fn smb_common_header(state: &SMBState, tx: &SMBTransaction) -> Json
+{
+    let js = Json::object();
+    js.set_integer("id", tx.id as u64);
+
+    if state.dialect != 0 {
+        let dialect = &smb2_dialect_string(state.dialect);
+        js.set_string("dialect", &dialect);
+    } else {
+        let dialect = match &state.dialect_vec {
+            &Some(ref d) => {
+                match str::from_utf8(&d) {
+                    Ok(v) => v,
+                    Err(_) => "invalid",
+                }
+            },
+            &None => { "unknown" },
+        };
+        js.set_string("dialect", &dialect);
+    }
+
+    match tx.vercmd.get_version() {
+        1 => {
+            let (ok, cmd) = tx.vercmd.get_smb1_cmd();
+            if ok {
+                js.set_string("command", &smb1_command_string(cmd));
+            }
+        },
+        2 => {
+            let (ok, cmd) = tx.vercmd.get_smb2_cmd();
+            if ok {
+                js.set_string("command", &smb2_command_string(cmd));
+            }
+        },
+        _ => { },
+    }
+
+    match tx.vercmd.get_ntstatus() {
+        (true, ntstatus) => {
+            let status = smb_ntstatus_string(ntstatus);
+            js.set_string("status", &status);
+            let status_hex = format!("0x{:x}", ntstatus);
+            js.set_string("statux", &status_hex);
+        },
+        (false, _) => {
+            match tx.vercmd.get_dos_error() {
+                (true, doserr) => {
+                    let status = smb_dos_error_string(doserr);
+                    js.set_string("status", &status);
+                    let status_hex = format!("0x{:x}", doserr);
+                    js.set_string("statux", &status_hex);
+                },
+                (_, _) => {
+                },
+            }
+        },
+    }
+
+
+    js.set_integer("session_id", tx.hdr.ssn_id);
+    js.set_integer("tree_id", tx.hdr.tree_id as u64);
+
+    js.set_boolean("request_done", tx.request_done);
+    js.set_boolean("response_done", tx.request_done);
+
+    match state.ntlmssp {
+        Some(ref ntlmssp) => {
+            let jsd = Json::object();
+            let domain = match str::from_utf8(&ntlmssp.domain) {
+                Ok(v) => v,
+                Err(_) => "UTF8_ERROR",
+            };
+            jsd.set_string("domain", &domain);
+
+            let user = match str::from_utf8(&ntlmssp.user) {
+                Ok(v) => v,
+                Err(_) => "UTF8_ERROR",
+            };
+            jsd.set_string("user", &user);
+
+            let host = match str::from_utf8(&ntlmssp.host) {
+                Ok(v) => v,
+                Err(_) => "UTF8_ERROR",
+            };
+            jsd.set_string("host", &host);
+            js.set("ntlmssp", jsd);
+        }
+        None => {},
+    }
+
+    match state.krb_ticket {
+        Some(ref ticket) => {
+            let jsd = Json::object();
+            let realm = match str::from_utf8(&ticket.realm) {
+                Ok(v) => v,
+                Err(_) => "UTF8_ERROR",
+            };
+            jsd.set_string("realm", &realm);
+            let jsa = Json::array();
+            for sname in &ticket.snames {
+                let name = match str::from_utf8(&sname) {
+                    Ok(v) => v,
+                    Err(_) => "UTF8_ERROR",
+                };
+                jsa.array_append_string(&name);
+            }
+            jsd.set("snames", jsa);
+            js.set("kerberos", jsd);
+        },
+        None => { },
+    }
+
+    match tx.type_data {
+        Some(SMBTransactionTypeData::CREATE(ref x)) => {
+            let mut name_raw = x.filename.to_vec();
+            name_raw.retain(|&i|i != 0x00);
+            let name = String::from_utf8_lossy(&name_raw);
+            if x.directory {
+                js.set_string("directory", &name);
+            } else {
+                js.set_string("file", &name);
+            }
+            match x.disposition {
+                1 => { js.set_string("disposition", "open"); },
+                2 => { js.set_string("disposition", "create"); },
+                5 => { js.set_string("disposition", "overwrite"); },
+                _ => { js.set_string("disposition", "UNKNOWN"); },
+            }
+            if x.delete_on_close {
+                js.set_string("access", "delete on close");
+            } else {
+                js.set_string("access", "normal");
+            }
+        },
+        Some(SMBTransactionTypeData::NEGOTIATE(ref x)) => {
+            if x.smb_ver == 1 {
+                let jsa = Json::array();
+                for d in &x.dialects {
+                    let dialect = String::from_utf8_lossy(&d);
+                    jsa.array_append_string(&dialect);
+                }
+                js.set("client_dialects", jsa);
+            } else if x.smb_ver == 2 {
+                let jsa = Json::array();
+                for d in &x.dialects2 {
+                    let dialect = String::from_utf8_lossy(&d);
+                    jsa.array_append_string(&dialect);
+                }
+                js.set("client_dialects", jsa);
+            }
+        },
+        Some(SMBTransactionTypeData::TREECONNECT(ref x)) => {
+            js.set_integer("tree_id", x.tree_id as u64);
+
+            let share_name = String::from_utf8_lossy(&x.share_name);
+            if x.is_pipe {
+                js.set_string("named_pipe", &share_name);
+            } else {
+                js.set_string("share", &share_name);
+            }
+        },
+        Some(SMBTransactionTypeData::FILE(ref x)) => {
+            let file_name = String::from_utf8_lossy(&x.file_name);
+            js.set_string("file", &file_name);
+            let share_name = String::from_utf8_lossy(&x.share_name);
+            js.set_string("share", &share_name);
+
+
+            if x.guid.len() >= 2 {
+                let fid_s = &x.guid[0..2];
+                let fid_n = match nom::le_u16(&fid_s) {
+                    nom::IResult::Done(_, x) => {
+                        x as u16
+                    }
+                    _ => 0 as u16
+                };
+                let fid_hex_str = format!("0x{:00x}", fid_n);
+                js.set_string("fid", &fid_hex_str);
+            }
+        },
+        Some(SMBTransactionTypeData::DCERPC(ref x)) => {
+            let jsd = Json::object();
+            jsd.set_string("request", &dcerpc_type_string(x.req_cmd));
+            if x.res_set {
+                jsd.set_string("response", &dcerpc_type_string(x.res_cmd));
+            } else {
+                jsd.set_string("response", "UNREPLIED");
+            }
+            match x.req_cmd {
+                DCERPC_TYPE_REQUEST => {
+                    jsd.set_integer("opnum", x.opnum as u64);
+                    let req = Json::object();
+                    req.set_integer("frag_cnt", x.frag_cnt_ts as u64);
+                    req.set_integer("stub_data_size", x.stub_data_ts.len() as u64);
+                    jsd.set("req", req);
+                    let res = Json::object();
+                    res.set_integer("frag_cnt", x.frag_cnt_tc as u64);
+                    res.set_integer("stub_data_size", x.stub_data_tc.len() as u64);
+                    jsd.set("res", res);
+                },
+                DCERPC_TYPE_BIND => {
+                    match state.dcerpc_ifaces {
+                        Some(ref ifaces) => {
+                            let jsa = Json::array();
+                            for i in ifaces {
+                                let jso = Json::object();
+                                let ifstr = dcerpc_uuid_to_string(&i);
+                                jso.set_string("uuid", &ifstr);
+                                let vstr = format!("{}.{}", i.ver, i.ver_min);
+                                jso.set_string("version", &vstr);
+
+                                if i.acked {
+                                    jso.set_integer("ack_result", i.ack_result as u64);
+                                    jso.set_integer("ack_reason", i.ack_reason as u64);
+                                }
+
+                                jsa.array_append(jso);
+                            }
+
+                            jsd.set("interfaces", jsa);
+                        },
+                        _ => {},
+                    }
+                },
+                _ => {},
+            }
+            jsd.set_integer("call_id", x.call_id as u64);
+            js.set("dcerpc", jsd);
+        }
+        _ => {  },
+    }
+    return js;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_smb_log_json_request(state: &mut SMBState, tx: &mut SMBTransaction) -> *mut JsonT
+{
+    let js = smb_common_header(state, tx);
+    return js.unwrap();
+}
+
+#[no_mangle]
+pub extern "C" fn rs_smb_log_json_response(state: &mut SMBState, tx: &mut SMBTransaction) -> *mut JsonT
+{
+    let js = smb_common_header(state, tx);
+    return js.unwrap();
+}
diff --git a/rust/src/smb/mod.rs b/rust/src/smb/mod.rs
new file mode 100644 (file)
index 0000000..31761dc
--- /dev/null
@@ -0,0 +1,36 @@
+/* Copyright (C) 2017 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+pub mod smb1_records;
+pub mod smb2_records;
+pub mod nbss_records;
+pub mod dcerpc_records;
+pub mod ntlmssp_records;
+
+pub mod smb;
+pub mod smb1;
+pub mod smb2;
+pub mod dcerpc;
+pub mod log;
+pub mod detect;
+pub mod debug;
+pub mod events;
+pub mod auth;
+pub mod files;
+
+//#[cfg(feature = "lua")]
+//pub mod lua;
diff --git a/rust/src/smb/nbss_records.rs b/rust/src/smb/nbss_records.rs
new file mode 100644 (file)
index 0000000..c594529
--- /dev/null
@@ -0,0 +1,58 @@
+/* Copyright (C) 2017 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use nom::{rest};
+
+#[derive(Debug,PartialEq)]
+pub struct NbssRecord<'a> {
+    pub message_type: u8,
+    pub length: u32,
+    pub data: &'a[u8],
+}
+
+named!(pub parse_nbss_record<NbssRecord>,
+   do_parse!(
+       type_and_len: bits!(tuple!(
+               take_bits!(u8, 8),
+               take_bits!(u32, 24)))
+       >> data: take!(type_and_len.1 as usize)
+       >> (NbssRecord {
+            message_type:type_and_len.0,
+            length:type_and_len.1,
+            data:data,
+        })
+));
+
+#[derive(Debug,PartialEq)]
+pub struct NbssRecordPartial<'a> {
+    pub message_type: u8,
+    pub length: u32,
+    pub data: &'a[u8],
+}
+
+named!(pub parse_nbss_record_partial<NbssRecordPartial>,
+   do_parse!(
+       type_and_len: bits!(tuple!(
+               take_bits!(u8, 8),
+               take_bits!(u32, 24)))
+       >> data: rest
+       >> (NbssRecordPartial {
+            message_type:type_and_len.0,
+            length:type_and_len.1,
+            data:data,
+        })
+));
diff --git a/rust/src/smb/ntlmssp_records.rs b/rust/src/smb/ntlmssp_records.rs
new file mode 100644 (file)
index 0000000..c47fa11
--- /dev/null
@@ -0,0 +1,85 @@
+/* Copyright (C) 2017 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use nom::{rest, le_u16, le_u32};
+
+#[derive(Debug,PartialEq)]
+pub struct NTLMSSPAuthRecord<'a> {
+    pub domain: &'a[u8],
+    pub user: &'a[u8],
+    pub host: &'a[u8],
+}
+
+named!(pub parse_ntlm_auth_record<NTLMSSPAuthRecord>,
+    dbg_dmp!(do_parse!(
+            lm_blob_len: le_u16
+         >> lm_blob_maxlen: le_u16
+         >> lm_blob_offset: le_u32
+
+         >> ntlmresp_blob_len: le_u16
+         >> ntlmresp_blob_maxlen: le_u16
+         >> ntlmresp_blob_offset: le_u32
+
+         >> domain_blob_len: le_u16
+         >> domain_blob_maxlen: le_u16
+         >> domain_blob_offset: le_u32
+
+         >> user_blob_len: le_u16
+         >> user_blob_maxlen: le_u16
+         >> user_blob_offset: le_u32
+
+         >> host_blob_len: le_u16
+         >> host_blob_maxlen: le_u16
+         >> host_blob_offset: le_u32
+
+         >> ssnkey_blob_len: le_u16
+         >> ssnkey_blob_maxlen: le_u16
+         >> ssnkey_blob_offset: le_u32
+
+         // subtrack 12 as idenfier (8) and type (4) are cut before we are called
+         // subtract 48 for the len/offset/maxlen fields above
+         >> take!(domain_blob_offset - (12 + 48))
+
+         //>> lm_blob: take!(lm_blob_len)
+         //>> ntlmresp_blob: take!(ntlmresp_blob_len)
+         >> domain_blob: take!(domain_blob_len)
+         >> user_blob: take!(user_blob_len)
+         >> host_blob: take!(host_blob_len)
+
+         >> ( NTLMSSPAuthRecord {
+                domain: domain_blob,
+                user: user_blob,
+                host: host_blob,
+            })
+)));
+
+#[derive(Debug,PartialEq)]
+pub struct NTLMSSPRecord<'a> {
+    pub msg_type: u32,
+    pub data: &'a[u8],
+}
+
+named!(pub parse_ntlmssp<NTLMSSPRecord>,
+    do_parse!(
+            take_until_and_consume!("NTLMSSP\x00")
+        >>  msg_type: le_u32
+        >>  data: rest
+        >>  (NTLMSSPRecord {
+                msg_type:msg_type,
+                data:data,
+            })
+));
diff --git a/rust/src/smb/smb.rs b/rust/src/smb/smb.rs
new file mode 100644 (file)
index 0000000..b99e013
--- /dev/null
@@ -0,0 +1,1798 @@
+/* Copyright (C) 2017 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/* TODO
+ * - check all parsers for calls on non-SUCCESS status
+ */
+
+/*  GAP processing:
+ *  - if post-gap we've seen a succesful tx req/res: we consider "re-sync'd"
+ */
+
+// written by Victor Julien
+extern crate libc;
+use std;
+use std::mem::transmute;
+use std::str;
+use std::ffi::CStr;
+
+use nom::IResult;
+use std::collections::HashMap;
+
+use core::*;
+use log::*;
+use applayer;
+use applayer::LoggerFlags;
+
+use smb::nbss_records::*;
+use smb::smb1_records::*;
+use smb::smb2_records::*;
+
+use smb::smb1::*;
+use smb::smb2::*;
+use smb::dcerpc::*;
+use smb::events::*;
+use smb::auth::*;
+use smb::files::*;
+
+pub static mut SURICATA_SMB_FILE_CONFIG: Option<&'static SuricataFileContext> = None;
+
+#[no_mangle]
+pub extern "C" fn rs_smb_init(context: &'static mut SuricataFileContext)
+{
+    unsafe {
+        SURICATA_SMB_FILE_CONFIG = Some(context);
+    }
+}
+
+pub const NBSS_MSGTYPE_SESSION_MESSAGE: u8 = 0x00;
+//const NBSS_MSGTYPE_SESSION_REQUEST: u8 = 0x81;
+
+pub const SMB_NTSTATUS_SUCCESS:                    u32 = 0;
+pub const SMB_NTSTATUS_PENDING:                    u32 = 0x00000103;
+pub const SMB_NTSTATUS_BUFFER_OVERFLOW:            u32 = 0x80000005;
+pub const SMB_NTSTATUS_NO_MORE_FILES:              u32 = 0x80000006;
+pub const SMB_NTSTATUS_NO_MORE_ENTRIES:            u32 = 0x8000001a;
+pub const SMB_NTSTATUS_INVALID_HANDLE:             u32 = 0xc0000008;
+pub const SMB_NTSTATUS_INVALID_PARAMETER:          u32 = 0xc000000d;
+pub const SMB_NTSTATUS_NO_SUCH_DEVICE:             u32 = 0xc000000e;
+pub const SMB_NTSTATUS_NO_SUCH_FILE:               u32 = 0xc000000f;
+pub const SMB_NTSTATUS_INVALID_DEVICE_REQUEST:     u32 = 0xc0000010;
+pub const SMB_NTSTATUS_END_OF_FILE:                u32 = 0xc0000011;
+pub const SMB_NTSTATUS_MORE_PROCESSING_REQUIRED:   u32 = 0xc0000016;
+pub const SMB_NTSTATUS_ACCESS_DENIED:              u32 = 0xc0000022;
+pub const SMB_NTSTATUS_OBJECT_NAME_INVALID:        u32 = 0xc0000033;
+pub const SMB_NTSTATUS_OBJECT_NAME_NOT_FOUND:      u32 = 0xc0000034;
+pub const SMB_NTSTATUS_OBJECT_NAME_COLLISION:      u32 = 0xc0000035;
+pub const SMB_NTSTATUS_OBJECT_PATH_NOT_FOUND:      u32 = 0xc000003a;
+pub const SMB_NTSTATUS_SHARING_VIOLATION:          u32 = 0xc0000043;
+pub const SMB_NTSTATUS_LOCK_CONFLICT:              u32 = 0xc0000054;
+pub const SMB_NTSTATUS_LOCK_NOT_GRANTED:           u32 = 0xc0000055;
+pub const SMB_NTSTATUS_PRIVILEGE_NOT_HELD:         u32 = 0xc0000061;
+pub const SMB_NTSTATUS_LOGON_FAILURE:              u32 = 0xc000006d;
+pub const SMB_NTSTATUS_PIPE_DISCONNECTED:          u32 = 0xc00000b0;
+pub const SMB_NTSTATUS_FILE_IS_A_DIRECTORY:        u32 = 0xc00000ba;
+pub const SMB_NTSTATUS_NOT_SUPPORTED:              u32 = 0xc00000bb;
+pub const SMB_NTSTATUS_BAD_NETWORK_NAME:           u32 = 0xc00000cc;
+pub const SMB_NTSTATUS_OPLOCK_NOT_GRANTED:         u32 = 0xc00000e2;
+pub const SMB_NTSTATUS_CANCELLED:                  u32 = 0xc0000120;
+pub const SMB_NTSTATUS_FILE_CLOSED:                u32 = 0xc0000128;
+pub const SMB_NTSTATUS_FS_DRIVER_REQUIRED:         u32 = 0xc000019c;
+pub const SMB_NTSTATUS_INSUFF_SERVER_RESOURCES:    u32 = 0xc0000205;
+pub const SMB_NTSTATUS_NOT_FOUND:                  u32 = 0xc0000225;
+pub const SMB_NTSTATUS_PIPE_BROKEN:                u32 = 0xc000014b;
+pub const SMB_NTSTATUS_TRUSTED_RELATIONSHIP_FAILURE:    u32 = 0xc000018d;
+pub const SMB_NTSTATUS_NOT_A_REPARSE_POINT:        u32 = 0xc0000275;
+pub const SMB_NTSTATUS_NETWORK_SESSION_EXPIRED:    u32 = 0xc000035c;
+
+pub fn smb_ntstatus_string(c: u32) -> String {
+    match c {
+        SMB_NTSTATUS_SUCCESS                   => "STATUS_SUCCESS",
+        SMB_NTSTATUS_BUFFER_OVERFLOW           => "STATUS_BUFFER_OVERFLOW",
+        SMB_NTSTATUS_PENDING                   => "STATUS_PENDING",
+        SMB_NTSTATUS_NO_MORE_FILES             => "STATUS_NO_MORE_FILES",
+        SMB_NTSTATUS_NO_MORE_ENTRIES           => "STATUS_NO_MORE_ENTRIES",
+        SMB_NTSTATUS_INVALID_HANDLE            => "STATUS_INVALID_HANDLE",
+        SMB_NTSTATUS_INVALID_PARAMETER         => "STATUS_INVALID_PARAMETER",
+        SMB_NTSTATUS_NO_SUCH_DEVICE            => "STATUS_NO_SUCH_DEVICE",
+        SMB_NTSTATUS_NO_SUCH_FILE              => "STATUS_NO_SUCH_FILE",
+        SMB_NTSTATUS_INVALID_DEVICE_REQUEST    => "STATUS_INVALID_DEVICE_REQUEST",
+        SMB_NTSTATUS_END_OF_FILE               => "STATUS_END_OF_FILE",
+        SMB_NTSTATUS_MORE_PROCESSING_REQUIRED  => "STATUS_MORE_PROCESSING_REQUIRED",
+        SMB_NTSTATUS_ACCESS_DENIED             => "STATUS_ACCESS_DENIED",
+        SMB_NTSTATUS_OBJECT_NAME_INVALID       => "STATUS_OBJECT_NAME_INVALID",
+        SMB_NTSTATUS_OBJECT_NAME_NOT_FOUND     => "STATUS_OBJECT_NAME_NOT_FOUND",
+        SMB_NTSTATUS_OBJECT_NAME_COLLISION     => "STATUS_OBJECT_NAME_COLLISION",
+        SMB_NTSTATUS_OBJECT_PATH_NOT_FOUND     => "STATUS_OBJECT_PATH_NOT_FOUND",
+        SMB_NTSTATUS_SHARING_VIOLATION         => "STATUS_SHARING_VIOLATION",
+        SMB_NTSTATUS_LOCK_CONFLICT             => "STATUS_LOCK_CONFLICT",
+        SMB_NTSTATUS_LOCK_NOT_GRANTED          => "STATUS_LOCK_NOT_GRANTED",
+        SMB_NTSTATUS_PRIVILEGE_NOT_HELD        => "STATUS_PRIVILEGE_NOT_HELD",
+        SMB_NTSTATUS_LOGON_FAILURE             => "STATUS_LOGON_FAILURE",
+        SMB_NTSTATUS_PIPE_DISCONNECTED         => "STATUS_PIPE_DISCONNECTED",
+        SMB_NTSTATUS_FILE_IS_A_DIRECTORY       => "STATUS_FILE_IS_A_DIRECTORY",
+        SMB_NTSTATUS_NOT_SUPPORTED             => "STATUS_NOT_SUPPORTED",
+        SMB_NTSTATUS_BAD_NETWORK_NAME          => "STATUS_BAD_NETWORK_NAME",
+        SMB_NTSTATUS_OPLOCK_NOT_GRANTED        => "STATUS_OPLOCK_NOT_GRANTED",
+        SMB_NTSTATUS_CANCELLED                 => "STATUS_CANCELLED",
+        SMB_NTSTATUS_FILE_CLOSED               => "STATUS_FILE_CLOSED",
+        SMB_NTSTATUS_FS_DRIVER_REQUIRED        => "STATUS_FS_DRIVER_REQUIRED",
+        SMB_NTSTATUS_INSUFF_SERVER_RESOURCES   => "STATUS_INSUFF_SERVER_RESOURCES",
+        SMB_NTSTATUS_NOT_FOUND                 => "STATUS_NOT_FOUND",
+        SMB_NTSTATUS_PIPE_BROKEN               => "STATUS_PIPE_BROKEN",
+        SMB_NTSTATUS_TRUSTED_RELATIONSHIP_FAILURE   => "STATUS_TRUSTED_RELATIONSHIP_FAILURE",
+        SMB_NTSTATUS_NOT_A_REPARSE_POINT       => "STATUS_NOT_A_REPARSE_POINT",
+        SMB_NTSTATUS_NETWORK_SESSION_EXPIRED   => "STATUS_NETWORK_SESSION_EXPIRED",
+        _ => { return (c).to_string(); },
+    }.to_string()
+}
+
+
+pub const SMB_DOS_SUCCESS:                u16 = 0;
+pub const SMB_DOS_BAD_FUNC:               u16 = 1;
+pub const SMB_DOS_BAD_FILE:               u16 = 2;
+pub const SMB_DOS_BAD_PATH:               u16 = 3;
+pub const SMB_DOS_TOO_MANY_OPEN_FILES:    u16 = 4;
+pub const SMB_DOS_ACCESS_DENIED:          u16 = 5;
+
+pub fn smb_dos_error_string(c: u16) -> String {
+    match c {
+        SMB_DOS_SUCCESS           => "DOS_SUCCESS",
+        SMB_DOS_BAD_FUNC          => "DOS_BAD_FUNC",
+        SMB_DOS_BAD_FILE          => "DOS_BAD_FILE",
+        SMB_DOS_BAD_PATH          => "DOS_BAD_PATH",
+        SMB_DOS_TOO_MANY_OPEN_FILES => "DOS_TOO_MANY_OPEN_FILES",
+        SMB_DOS_ACCESS_DENIED     => "DOS_ACCESS_DENIED",
+        _ => { return (c).to_string(); },
+    }.to_string()
+}
+
+pub const NTLMSSP_NEGOTIATE:               u32 = 1;
+pub const NTLMSSP_CHALLENGE:               u32 = 2;
+pub const NTLMSSP_AUTH:                    u32 = 3;
+
+pub fn ntlmssp_type_string(c: u32) -> String {
+    match c {
+        NTLMSSP_NEGOTIATE   => "NTLMSSP_NEGOTIATE",
+        NTLMSSP_CHALLENGE   => "NTLMSSP_CHALLENGE",
+        NTLMSSP_AUTH        => "NTLMSSP_AUTH",
+        _ => { return (c).to_string(); },
+    }.to_string()
+}
+
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub struct SMBVerCmdStat {
+    smb_ver: u8,
+    smb1_cmd: u8,
+    smb2_cmd: u16,
+
+    status_set: bool,
+    status_is_dos_error: bool,
+    status: u32,
+}
+
+impl SMBVerCmdStat {
+    pub fn new() -> SMBVerCmdStat {
+        return SMBVerCmdStat {
+            smb_ver: 0,
+            smb1_cmd: 0,
+            smb2_cmd: 0,
+            status_set: false,
+            status_is_dos_error: false,
+            status: 0,
+        }
+    }
+    pub fn new1(cmd: u8) -> SMBVerCmdStat {
+        return SMBVerCmdStat {
+            smb_ver: 1,
+            smb1_cmd: cmd,
+            smb2_cmd: 0,
+            status_set: false,
+            status_is_dos_error: false,
+            status: 0,
+        }
+    }
+    pub fn new1_with_ntstatus(cmd: u8, status: u32) -> SMBVerCmdStat {
+        return SMBVerCmdStat {
+            smb_ver: 1,
+            smb1_cmd: cmd,
+            smb2_cmd: 0,
+            status_set: true,
+            status_is_dos_error: false,
+            status: status,
+        }
+    }
+    pub fn new2(cmd: u16) -> SMBVerCmdStat {
+        return SMBVerCmdStat {
+            smb_ver: 2,
+            smb1_cmd: 0,
+            smb2_cmd: cmd,
+            status_set: false,
+            status_is_dos_error: false,
+            status: 0,
+        }
+    }
+
+    pub fn new2_with_ntstatus(cmd: u16, status: u32) -> SMBVerCmdStat {
+        return SMBVerCmdStat {
+            smb_ver: 2,
+            smb1_cmd: 0,
+            smb2_cmd: cmd,
+            status_set: true,
+            status_is_dos_error: false,
+            status: status,
+        }
+    }
+
+    pub fn set_smb1_cmd(&mut self, cmd: u8) -> bool {
+        if self.smb_ver != 0 {
+            return false;
+        }
+        self.smb_ver = 1;
+        self.smb1_cmd = cmd;
+        return true;
+    }
+
+    pub fn set_smb2_cmd(&mut self, cmd: u16) -> bool {
+        if self.smb_ver != 0 {
+            return false;
+        }
+        self.smb_ver = 2;
+        self.smb2_cmd = cmd;
+        return true;
+    }
+
+    pub fn get_version(&self) -> u8 {
+        self.smb_ver
+    }
+
+    pub fn get_smb1_cmd(&self) -> (bool, u8) {
+        if self.smb_ver != 1 {
+            return (false, 0);
+        }
+        return (true, self.smb1_cmd);
+    }
+
+    pub fn get_smb2_cmd(&self) -> (bool, u16) {
+        if self.smb_ver != 2 {
+            return (false, 0);
+        }
+        return (true, self.smb2_cmd);
+    }
+
+    pub fn get_ntstatus(&self) -> (bool, u32) {
+        (self.status_set && !self.status_is_dos_error, self.status)
+    }
+
+    pub fn get_dos_error(&self) -> (bool, u16) {
+        (self.status_set && self.status_is_dos_error, self.status as u16)
+    }
+
+    fn set_status(&mut self, status: u32, is_dos_error: bool)
+    {
+        if is_dos_error {
+            self.status_is_dos_error = true;
+            self.status = (status & 0xffff_0000) >> 16;
+        } else {
+            self.status = status;
+        }
+        self.status_set = true;
+    }
+
+    pub fn set_ntstatus(&mut self, status: u32)
+    {
+        self.set_status(status, false)
+    }
+
+    pub fn set_status_dos_error(&mut self, status: u32)
+    {
+        self.set_status(status, true)
+    }
+}
+
+#[derive(Debug)]
+pub enum SMBTransactionTypeData {
+    FILE(SMBTransactionFile),
+    TREECONNECT(SMBTransactionTreeConnect),
+    NEGOTIATE(SMBTransactionNegotiate),
+    DCERPC(SMBTransactionDCERPC),
+    CREATE(SMBTransactionCreate),
+}
+
+#[derive(Debug)]
+pub struct SMBTransactionCreate {
+    pub disposition: u32,
+    pub delete_on_close: bool,
+    pub directory: bool,
+    pub filename: Vec<u8>,
+}
+
+impl SMBTransactionCreate {
+    pub fn new(filename: Vec<u8>, disp: u32, del: bool, dir: bool) -> SMBTransactionCreate {
+        return SMBTransactionCreate {
+            disposition: disp,
+            delete_on_close: del,
+            directory: dir,
+            filename: filename,
+        }
+    }
+}
+
+#[derive(Debug)]
+pub struct SMBTransactionNegotiate {
+    pub smb_ver: u8,
+    pub dialects: Vec<Vec<u8>>,
+    pub dialects2: Vec<Vec<u8>>,
+}
+
+impl SMBTransactionNegotiate {
+    pub fn new(smb_ver: u8) -> SMBTransactionNegotiate {
+        return SMBTransactionNegotiate {
+            smb_ver: smb_ver,
+            dialects: Vec::new(),
+            dialects2: Vec::new(),
+        }
+    }
+}
+
+#[derive(Debug)]
+pub struct SMBTransactionTreeConnect {
+    pub is_pipe: bool,
+    pub tree_id: u32,
+    pub share_name: Vec<u8>,
+}
+
+impl SMBTransactionTreeConnect {
+    pub fn new(share_name: Vec<u8>) -> SMBTransactionTreeConnect {
+        return SMBTransactionTreeConnect {
+            is_pipe:false,
+            tree_id:0,
+            share_name:share_name,
+        }
+    }
+}
+
+#[derive(Debug)]
+pub struct SMBTransaction {
+    pub id: u64,    /// internal id
+
+    /// version, command and status
+    pub vercmd: SMBVerCmdStat,
+    /// session id, tree id, etc.
+    pub hdr: SMBCommonHdr,
+
+    /// for state tracking. false means this side is in progress, true
+    /// that it's complete.
+    pub request_done: bool,
+    pub response_done: bool,
+
+    /// Command specific data
+    pub type_data: Option<SMBTransactionTypeData>,
+
+    /// detection engine flags for use by detection engine
+    detect_flags_ts: u64,
+    detect_flags_tc: u64,
+    pub logged: LoggerFlags,
+    pub de_state: Option<*mut DetectEngineState>,
+    pub events: *mut AppLayerDecoderEvents,
+}
+
+impl SMBTransaction {
+    pub fn new() -> SMBTransaction {
+        return SMBTransaction{
+            id: 0,
+            vercmd: SMBVerCmdStat::new(),
+            hdr: SMBCommonHdr::init(),
+            request_done: false,
+            response_done: false,
+            type_data: None,
+            detect_flags_ts: 0,
+            detect_flags_tc: 0,
+            logged: LoggerFlags::new(),
+            de_state: None,
+            events: std::ptr::null_mut(),
+        }
+    }
+
+    pub fn set_status(&mut self, status: u32, is_dos_error: bool)
+    {
+        if is_dos_error {
+            self.vercmd.set_status_dos_error(status);
+        } else {
+            self.vercmd.set_ntstatus(status);
+        }
+    }
+
+    pub fn free(&mut self) {
+        if self.events != std::ptr::null_mut() {
+            sc_app_layer_decoder_events_free_events(&mut self.events);
+        }
+        match self.de_state {
+            Some(state) => {
+                sc_detect_engine_state_free(state);
+            }
+            _ => {}
+        }
+    }
+}
+
+impl Drop for SMBTransaction {
+    fn drop(&mut self) {
+        self.free();
+    }
+}
+
+#[derive(Hash, Eq, PartialEq, Debug, Clone)]
+pub struct SMBFileGUIDOffset {
+    pub guid: Vec<u8>,
+    pub offset: u64,
+}
+
+impl SMBFileGUIDOffset {
+    pub fn new(guid: Vec<u8>, offset: u64) -> SMBFileGUIDOffset {
+        SMBFileGUIDOffset {
+            guid:guid,
+            offset:offset,
+        }
+    }
+}
+
+/// type values to make sure we're not mixing things
+/// up in hashmap lookups
+pub const SMBHDR_TYPE_GUID:        u32 = 1;
+pub const SMBHDR_TYPE_SHARE:       u32 = 2;
+pub const SMBHDR_TYPE_FILENAME:    u32 = 3;
+pub const SMBHDR_TYPE_OFFSET:      u32 = 4;
+pub const SMBHDR_TYPE_GENERICTX:   u32 = 5;
+pub const SMBHDR_TYPE_HEADER:      u32 = 6;
+pub const SMBHDR_TYPE_MAX_SIZE:    u32 = 7; // max resp size for SMB1_COMMAND_TRANS
+pub const SMBHDR_TYPE_TXNAME:      u32 = 8; // SMB1_COMMAND_TRANS tx_name
+pub const SMBHDR_TYPE_TRANS_FRAG:  u32 = 9;
+pub const SMBHDR_TYPE_TREE:        u32 = 10;
+pub const SMBHDR_TYPE_DCERPCTX:    u32 = 11;
+
+#[derive(Hash, Eq, PartialEq, Debug)]
+pub struct SMBCommonHdr {
+    pub ssn_id: u64,
+    pub tree_id: u32,
+    pub rec_type: u32,
+    pub msg_id: u64,
+}
+
+impl SMBCommonHdr {
+    pub fn init() -> SMBCommonHdr {
+        SMBCommonHdr {
+            rec_type : 0,
+            ssn_id : 0,
+            tree_id : 0,
+            msg_id : 0,
+        }
+    }
+    pub fn new(rec_type: u32, ssn_id: u64, tree_id: u32, msg_id: u64) -> SMBCommonHdr {
+        SMBCommonHdr {
+            rec_type : rec_type,
+            ssn_id : ssn_id,
+            tree_id : tree_id,
+            msg_id : msg_id,
+        }
+    }
+    pub fn from2(r: &Smb2Record, rec_type: u32) -> SMBCommonHdr {
+        let tree_id = match rec_type {
+            SMBHDR_TYPE_TREE => { 0 },
+            _ => r.tree_id,
+        };
+        let msg_id = match rec_type {
+            SMBHDR_TYPE_TRANS_FRAG => { 0 },
+            SMBHDR_TYPE_SHARE => { 0 },
+            _ => { r.message_id as u64 },
+        };
+
+        SMBCommonHdr {
+            rec_type : rec_type,
+            ssn_id : r.session_id,
+            tree_id : tree_id,
+            msg_id : msg_id,
+        }
+
+    }
+    pub fn from1(r: &SmbRecord, rec_type: u32) -> SMBCommonHdr {
+        let tree_id = match rec_type {
+            SMBHDR_TYPE_TREE => { 0 },
+            _ => r.tree_id as u32,
+        };
+        let msg_id = match rec_type {
+            SMBHDR_TYPE_TRANS_FRAG => { 0 },
+            SMBHDR_TYPE_SHARE => { 0 },
+            _ => { r.multiplex_id as u64 },
+        };
+
+        SMBCommonHdr {
+            rec_type : rec_type,
+            ssn_id : r.ssn_id as u64,
+            tree_id : tree_id,
+            msg_id : msg_id,
+        }
+    }
+}
+
+#[derive(Hash, Eq, PartialEq, Debug)]
+pub struct SMBHashKeyHdrGuid {
+    hdr: SMBCommonHdr,
+    guid: Vec<u8>,
+}
+
+impl SMBHashKeyHdrGuid {
+    pub fn new(hdr: SMBCommonHdr, guid: Vec<u8>) -> SMBHashKeyHdrGuid {
+        SMBHashKeyHdrGuid {
+            hdr: hdr, guid: guid,
+        }
+    }
+}
+
+#[derive(Hash, Eq, PartialEq, Debug)]
+pub struct SMBTree {
+    pub name: Vec<u8>,
+    pub is_pipe: bool,
+}
+
+impl SMBTree {
+    pub fn new(name: Vec<u8>, is_pipe: bool) -> SMBTree {
+        SMBTree {
+            name:name,
+            is_pipe:is_pipe,
+        }
+    }
+}
+
+pub fn u32_as_bytes(i: u32) -> [u8;4] {
+    let o1: u8 = ((i >> 24) & 0xff) as u8;
+    let o2: u8 = ((i >> 16) & 0xff) as u8;
+    let o3: u8 = ((i >> 8)  & 0xff) as u8;
+    let o4: u8 =  (i        & 0xff) as u8;
+    return [o1, o2, o3, o4]
+}
+
+pub struct SMBState<> {
+    /// map ssn/tree/msgid to vec (guid/name/share)
+    pub ssn2vec_map: HashMap<SMBCommonHdr, Vec<u8>>,
+    /// map guid to filename
+    pub guid2name_map: HashMap<Vec<u8>, Vec<u8>>,
+    /// map ssn key to read offset
+    pub ssn2vecoffset_map: HashMap<SMBCommonHdr, SMBFileGUIDOffset>,
+
+    pub ssn2tree_map: HashMap<SMBCommonHdr, SMBTree>,
+
+    // track the max size we expect for TRANS responses
+    pub ssn2maxsize_map: HashMap<SMBCommonHdr, u16>,
+    pub ssnguid2vec_map: HashMap<SMBHashKeyHdrGuid, Vec<u8>>,
+
+    /// TCP segments defragmentation buffer
+    pub tcp_buffer_ts: Vec<u8>,
+    pub tcp_buffer_tc: Vec<u8>,
+
+    pub files: SMBFiles,
+
+    pub skip_ts: u32,
+    pub skip_tc: u32,
+
+    pub file_ts_left : u32,
+    pub file_tc_left : u32,
+    pub file_ts_guid : Vec<u8>,
+    pub file_tc_guid : Vec<u8>,
+
+    pub ts_ssn_gap: bool,
+    pub tc_ssn_gap: bool,
+
+    pub ts_gap: bool, // last TS update was gap
+    pub tc_gap: bool, // last TC update was gap
+
+    ts_trunc: bool, // no more data for TOSERVER
+    tc_trunc: bool, // no more data for TOCLIENT
+
+    /// transactions list
+    pub transactions: Vec<SMBTransaction>,
+
+    /// tx counter for assigning incrementing id's to tx's
+    tx_id: u64,
+
+    pub dialect: u16,
+    pub dialect_vec: Option<Vec<u8>>, // used if dialect == 0
+    pub dialects: Option<Vec<Vec<u8>>>,
+
+    /// dcerpc interfaces, stored here to be able to match
+    /// them while inspecting DCERPC REQUEST txs
+    pub dcerpc_ifaces: Option<Vec<DCERPCIface>>,
+
+    pub ntlmssp: Option<NtlmsspData>,
+    pub krb_ticket: Option<Kerberos5Ticket>,
+}
+
+impl SMBState {
+    /// Allocation function for a new TLS parser instance
+    pub fn new() -> SMBState {
+        SMBState {
+            ssn2vec_map:HashMap::new(),
+            guid2name_map:HashMap::new(),
+            ssn2vecoffset_map:HashMap::new(),
+            ssn2tree_map:HashMap::new(),
+            ssn2maxsize_map:HashMap::new(),
+            ssnguid2vec_map:HashMap::new(),
+            tcp_buffer_ts:Vec::new(),
+            tcp_buffer_tc:Vec::new(),
+            files: SMBFiles::new(),
+            skip_ts:0,
+            skip_tc:0,
+            file_ts_left:0,
+            file_tc_left:0,
+            file_ts_guid:Vec::new(),
+            file_tc_guid:Vec::new(),
+            ts_ssn_gap: false,
+            tc_ssn_gap: false,
+            ts_gap: false,
+            tc_gap: false,
+            ts_trunc: false,
+            tc_trunc: false,
+            transactions: Vec::new(),
+            tx_id:0,
+            dialect:0,
+            dialect_vec: None,
+            dialects: None,
+            dcerpc_ifaces: None,
+            ntlmssp: None,
+            krb_ticket: None,
+        }
+    }
+
+    pub fn free(&mut self) {
+        //self._debug_state_stats();
+        self._debug_tx_stats();
+        self.files.free();
+    }
+
+    pub fn new_tx(&mut self) -> SMBTransaction {
+        let mut tx = SMBTransaction::new();
+        self.tx_id += 1;
+        tx.id = self.tx_id;
+        SCLogDebug!("TX {} created", tx.id);
+        return tx;
+    }
+
+    pub fn free_tx(&mut self, tx_id: u64) {
+        SCLogDebug!("Freeing TX with ID {} TX.ID {}", tx_id, tx_id+1);
+        let len = self.transactions.len();
+        let mut found = false;
+        let mut index = 0;
+        for i in 0..len {
+            let tx = &self.transactions[i];
+            if tx.id == tx_id + 1 {
+                found = true;
+                index = i;
+                SCLogDebug!("tx {} progress {}/{}", tx.id, tx.request_done, tx.response_done);
+                break;
+            }
+        }
+        if found {
+            SCLogDebug!("freeing TX with ID {} TX.ID {} at index {} left: {} max id: {}",
+                    tx_id, tx_id+1, index, self.transactions.len(), self.tx_id);
+            self.transactions.remove(index);
+        }
+    }
+
+    // for use with the C API call StateGetTxIterator
+    pub fn get_tx_iterator(&mut self, min_tx_id: u64, state: &mut u64) ->
+        Option<(&SMBTransaction, u64, bool)>
+    {
+        let mut index = *state as usize;
+        let len = self.transactions.len();
+
+        // find tx that is >= min_tx_id
+        while index < len {
+            let tx = &self.transactions[index];
+            if tx.id < min_tx_id + 1 {
+                index += 1;
+                continue;
+            }
+            *state = index as u64 + 1;
+            //SCLogDebug!("returning tx_id {} has_next? {} (len {} index {}), tx {:?}",
+            //        tx.id - 1, (len - index) > 1, len, index, tx);
+            return Some((tx, tx.id - 1, (len - index) > 1));
+        }
+        return None;
+    }
+
+    pub fn get_tx_by_id(&mut self, tx_id: u64) -> Option<&SMBTransaction> {
+/*
+        if self.transactions.len() > 100 {
+            SCLogNotice!("get_tx_by_id: tx_id={} in list={}", tx_id, self.transactions.len());
+            self._dump_txs();
+            panic!("txs exploded");
+        }
+*/
+        for tx in &mut self.transactions {
+            if tx.id == tx_id + 1 {
+                let ver = tx.vercmd.get_version();
+                let mut _smbcmd;
+                if ver == 2 {
+                    let (_, cmd) = tx.vercmd.get_smb2_cmd();
+                    _smbcmd = cmd;
+                } else {
+                    let (_, cmd) = tx.vercmd.get_smb1_cmd();
+                    _smbcmd = cmd as u16;
+                }
+                SCLogDebug!("Found SMB TX: id {} ver:{} cmd:{} progress {}/{} type_data {:?}",
+                        tx.id, ver, _smbcmd, tx.request_done, tx.response_done, tx.type_data);
+                return Some(tx);
+            }
+        }
+        SCLogDebug!("Failed to find SMB TX with ID {}", tx_id);
+        return None;
+    }
+
+    /* generic TX has no type_data and is only used to
+     * track a single cmd request/reply pair. */
+
+    pub fn new_generic_tx(&mut self, smb_ver: u8, smb_cmd: u16, key: SMBCommonHdr)
+        -> (&mut SMBTransaction)
+    {
+        let mut tx = self.new_tx();
+        if smb_ver == 1 && smb_cmd <= 255 {
+            tx.vercmd.set_smb1_cmd(smb_cmd as u8);
+        } else if smb_ver == 2 {
+            tx.vercmd.set_smb2_cmd(smb_cmd);
+        }
+
+        tx.type_data = None;
+        tx.request_done = true;
+        tx.response_done = self.tc_trunc; // no response expected if tc is truncated
+        tx.hdr = key;
+
+        SCLogDebug!("SMB: TX GENERIC created: ID {} tx list {} {:?}",
+                tx.id, self.transactions.len(), &tx);
+        self.transactions.push(tx);
+        let tx_ref = self.transactions.last_mut();
+        return tx_ref.unwrap();
+    }
+
+    pub fn get_last_tx(&mut self, smb_ver: u8, smb_cmd: u16)
+        -> Option<&mut SMBTransaction>
+    {
+        let tx_ref = self.transactions.last_mut();
+        match tx_ref {
+            Some(tx) => {
+                let found = if tx.vercmd.get_version() == smb_ver {
+                    if smb_ver == 1 {
+                        let (_, cmd) = tx.vercmd.get_smb1_cmd();
+                        cmd as u16 == smb_cmd
+                    } else if smb_ver == 2 {
+                        let (_, cmd) = tx.vercmd.get_smb2_cmd();
+                        cmd == smb_cmd
+                    } else {
+                        false
+                    }
+                } else {
+                    false
+                };
+                if found {
+                    return Some(tx);
+                }
+            },
+            None => { },
+        }
+        return None;
+    }
+
+    pub fn get_generic_tx(&mut self, smb_ver: u8, smb_cmd: u16, key: &SMBCommonHdr)
+        -> Option<&mut SMBTransaction>
+    {
+        for tx in &mut self.transactions {
+            let found = if tx.vercmd.get_version() == smb_ver {
+                if smb_ver == 1 {
+                    let (_, cmd) = tx.vercmd.get_smb1_cmd();
+                    cmd as u16 == smb_cmd && tx.hdr == *key
+                } else if smb_ver == 2 {
+                    let (_, cmd) = tx.vercmd.get_smb2_cmd();
+                    cmd == smb_cmd && tx.hdr == *key
+                } else {
+                    false
+                }
+            } else {
+                false
+            };
+            if found {
+                return Some(tx);
+            }
+        }
+        return None;
+    }
+
+    pub fn new_negotiate_tx(&mut self, smb_ver: u8)
+        -> (&mut SMBTransaction)
+    {
+        let mut tx = self.new_tx();
+        if smb_ver == 1 {
+            tx.vercmd.set_smb1_cmd(SMB1_COMMAND_NEGOTIATE_PROTOCOL);
+        } else if smb_ver == 2 {
+            tx.vercmd.set_smb2_cmd(SMB2_COMMAND_NEGOTIATE_PROTOCOL);
+        }
+
+        tx.type_data = Some(SMBTransactionTypeData::NEGOTIATE(
+                    SMBTransactionNegotiate::new(smb_ver)));
+        tx.request_done = true;
+        tx.response_done = self.tc_trunc; // no response expected if tc is truncated
+
+        SCLogDebug!("SMB: TX NEGOTIATE created: ID {} SMB ver {}", tx.id, smb_ver);
+        self.transactions.push(tx);
+        let tx_ref = self.transactions.last_mut();
+        return tx_ref.unwrap();
+    }
+
+    pub fn get_negotiate_tx(&mut self, smb_ver: u8)
+        -> Option<&mut SMBTransaction>
+    {
+        for tx in &mut self.transactions {
+            let found = match tx.type_data {
+                Some(SMBTransactionTypeData::NEGOTIATE(ref x)) => {
+                    if x.smb_ver == smb_ver {
+                        true
+                    } else {
+                        false
+                    }
+                },
+                _ => { false },
+            };
+            if found {
+                return Some(tx);
+            }
+        }
+        return None;
+    }
+
+    pub fn new_treeconnect_tx(&mut self, hdr: SMBCommonHdr, name: Vec<u8>)
+        -> (&mut SMBTransaction)
+    {
+        let mut tx = self.new_tx();
+
+        tx.hdr = hdr;
+        tx.type_data = Some(SMBTransactionTypeData::TREECONNECT(
+                    SMBTransactionTreeConnect::new(name.to_vec())));
+        tx.request_done = true;
+        tx.response_done = self.tc_trunc; // no response expected if tc is truncated
+
+        SCLogDebug!("SMB: TX TREECONNECT created: ID {} NAME {}",
+                tx.id, String::from_utf8_lossy(&name));
+        self.transactions.push(tx);
+        let tx_ref = self.transactions.last_mut();
+        return tx_ref.unwrap();
+    }
+
+    pub fn get_treeconnect_tx(&mut self, hdr: SMBCommonHdr)
+        -> Option<&mut SMBTransaction>
+    {
+        for tx in &mut self.transactions {
+            let hit = tx.hdr == hdr && match tx.type_data {
+                Some(SMBTransactionTypeData::TREECONNECT(_)) => { true },
+                _ => { false },
+            };
+            if hit {
+                return Some(tx);
+            }
+        }
+        return None;
+    }
+
+    pub fn new_create_tx(&mut self, file_name: &Vec<u8>,
+            disposition: u32, del: bool, dir: bool,
+            hdr: SMBCommonHdr)
+        -> &mut SMBTransaction
+    {
+        let mut tx = self.new_tx();
+        tx.hdr = hdr;
+        tx.type_data = Some(SMBTransactionTypeData::CREATE(
+                            SMBTransactionCreate::new(
+                                file_name.to_vec(), disposition,
+                                del, dir)));
+        tx.request_done = true;
+        tx.response_done = self.tc_trunc; // no response expected if tc is truncated
+
+        self.transactions.push(tx);
+        let tx_ref = self.transactions.last_mut();
+        return tx_ref.unwrap();
+    }
+
+    pub fn get_create_tx_by_hdr(&mut self, hdr: &SMBCommonHdr)
+        -> Option<&mut SMBTransaction>
+    {
+        for tx in &mut self.transactions {
+            let found = match tx.type_data {
+                Some(SMBTransactionTypeData::CREATE(ref _d)) => {
+                    *hdr == tx.hdr
+                },
+                _ => { false },
+            };
+
+            if found {
+                SCLogDebug!("SMB: Found SMB create TX with ID {}", tx.id);
+                return Some(tx);
+            }
+        }
+        SCLogDebug!("SMB: Failed to find SMB create TX with key {:?}", hdr);
+        return None;
+    }
+
+    pub fn get_service_for_guid(&self, guid: &[u8]) -> (&'static str, bool)
+    {
+        let (name, is_dcerpc) = match self.guid2name_map.get(&guid.to_vec()) {
+            Some(n) => {
+                match str::from_utf8(&n) {
+                    Ok("PSEXESVC") => ("PSEXESVC", false),
+                    Ok("svcctl") => ("svcctl", true),
+                    Ok("srvsvc") => ("srvsvc", true),
+                    Ok("atsvc") => ("atsvc", true),
+                    Ok("lsarpc") => ("lsarpc", true),
+                    Ok("samr") => ("samr", true),
+                    Err(_) => ("MALFORMED", false),
+                    Ok(&_) => {
+                        SCLogNotice!("don't know {}", String::from_utf8_lossy(&n));
+                        ("UNKNOWN", false)
+                    },
+                }
+            },
+            _ => { ("UNKNOWN", false) },
+        };
+        SCLogDebug!("service {} is_dcerpc {}", name, is_dcerpc);
+        (&name, is_dcerpc)
+    }
+
+    /* if we have marked the ssn as 'gapped' we check to see if
+     * we've caught up. The check is to see if we have the last
+     * tx in our list is smaller than the max id. This means we've
+     * seen a tx that has been fully processed and removed. */
+    pub fn check_gap_resync(&mut self)
+    {
+        if self.ts_ssn_gap || self.tc_ssn_gap {
+            let max_id = self.tx_id;
+            let over = match self.transactions.last() {
+                Some(tx) => {
+                    SCLogDebug!("tx.id {} max_id {}", tx.id, max_id);
+                    tx.id < max_id
+                },
+                None => { false },
+            };
+            if over {
+                SCLogDebug!("post-GAP resync confirmed");
+                self.ts_ssn_gap = false;
+                self.tc_ssn_gap = false;
+                self.close_non_file_txs();
+            }
+        }
+    }
+
+    /* close all txs execpt file xfers. */
+    fn close_non_file_txs(&mut self) {
+        SCLogDebug!("checking for non-file txs to wrap up");
+        for tx in &mut self.transactions {
+            match tx.type_data {
+                None => {
+                    SCLogDebug!("tx {} marked as done", tx.id);
+                    tx.request_done = true;
+                    tx.response_done = true;
+                },
+                     _ => { },
+            }
+        }
+    }
+
+    // return how much data we consumed
+    fn handle_skip(&mut self, direction: u8, input_size: u32) -> u32 {
+        let mut skip_left = if direction == STREAM_TOSERVER {
+            self.skip_ts
+        } else {
+            self.skip_tc
+        };
+        if skip_left == 0 {
+            return 0
+        }
+        SCLogDebug!("skip_left {} input_size {}", skip_left, input_size);
+
+        let consumed = if skip_left >= input_size {
+            input_size
+        } else {
+            skip_left
+        };
+
+        if skip_left <= input_size {
+            skip_left = 0;
+        } else {
+            skip_left -= input_size;
+        }
+
+        if direction == STREAM_TOSERVER {
+            self.skip_ts = skip_left;
+        } else {
+            self.skip_tc = skip_left;
+        }
+        return consumed;
+    }
+
+    /// return bytes consumed
+    pub fn parse_tcp_data_ts_partial<'b>(&mut self, input: &'b[u8]) -> usize
+    {
+        SCLogDebug!("incomplete of size {}", input.len());
+        if input.len() < 512 {
+            return 0;
+        }
+
+        match parse_nbss_record_partial(input) {
+            IResult::Done(output, ref nbss_part_hdr) => {
+                SCLogDebug!("parse_nbss_record_partial ok, output len {}", output.len());
+                if nbss_part_hdr.message_type == NBSS_MSGTYPE_SESSION_MESSAGE {
+                    match parse_smb_version(&nbss_part_hdr.data) {
+                        IResult::Done(_, ref smb) => {
+                            SCLogDebug!("SMB {:?}", smb);
+                            if smb.version == 255u8 { // SMB1
+                                SCLogDebug!("SMBv1 record");
+                                match parse_smb_record(&nbss_part_hdr.data) {
+                                    IResult::Done(_, ref r) => {
+                                        if r.command == SMB1_COMMAND_WRITE_ANDX {
+                                            // see if it's a write to a pipe. We only handle those
+                                            // if complete.
+                                            let tree_key = SMBCommonHdr::new(SMBHDR_TYPE_SHARE,
+                                                    r.ssn_id as u64, r.tree_id as u32, 0);
+                                            let is_pipe = match self.ssn2tree_map.get(&tree_key) {
+                                                Some(n) => n.is_pipe,
+                                                None => false,
+                                            };
+                                            if is_pipe {
+                                                return 0;
+                                            }
+                                            smb1_write_request_record(self, r);
+                                            let consumed = input.len() - output.len();
+                                            return consumed;
+                                        }
+                                    },
+                                    _ => { },
+
+                                }
+                            } else if smb.version == 254u8 { // SMB2
+                                SCLogDebug!("SMBv2 record");
+                                match parse_smb2_request_record(&nbss_part_hdr.data) {
+                                    IResult::Done(_, ref smb_record) => {
+                                        SCLogDebug!("SMB2: partial record {}",
+                                                &smb2_command_string(smb_record.command));
+                                        if smb_record.command == SMB2_COMMAND_WRITE {
+                                            smb2_write_request_record(self, smb_record);
+                                            let consumed = input.len() - output.len();
+                                            return consumed;
+                                        }
+                                    },
+                                    _ => { },
+                                }
+                            }
+                        },
+                        _ => { },
+                    }
+                }
+            },
+            _ => { },
+        }
+
+        return 0;
+    }
+
+    /// Parsing function, handling TCP chunks fragmentation
+    pub fn parse_tcp_data_ts<'b>(&mut self, i: &'b[u8]) -> u32
+    {
+        self.check_gap_resync();
+
+        let mut v : Vec<u8>;
+        //println!("parse_tcp_data_ts ({})",i.len());
+        //println!("{:?}",i);
+        // Check if TCP data is being defragmented
+        let tcp_buffer = match self.tcp_buffer_ts.len() {
+            0 => i,
+            _ => {
+                v = self.tcp_buffer_ts.split_off(0);
+                if self.tcp_buffer_ts.len() + i.len() > 100000 {
+                    self.set_event(SMBEvent::RecordOverflow);
+                    return 1;
+                };
+                v.extend_from_slice(i);
+                v.as_slice()
+            },
+        };
+        //println!("tcp_buffer ({})",tcp_buffer.len());
+        let mut cur_i = tcp_buffer;
+        if cur_i.len() > 1000000 {
+            self.set_event(SMBEvent::RecordOverflow);
+            return 1;
+        }
+        let consumed = self.handle_skip(STREAM_TOSERVER, cur_i.len() as u32);
+        if consumed > 0 {
+            if consumed > cur_i.len() as u32 {
+                self.set_event(SMBEvent::InternalError);
+                return 1;
+            }
+            cur_i = &cur_i[consumed as usize..];
+        }
+        // take care of in progress file chunk transfers
+        // and skip buffer beyond it
+        let consumed = self.filetracker_update(STREAM_TOSERVER, cur_i, 0);
+        if consumed > 0 {
+            if consumed > cur_i.len() as u32 {
+                self.set_event(SMBEvent::InternalError);
+                return 1;
+            }
+            cur_i = &cur_i[consumed as usize..];
+        }
+        // gap
+        if self.ts_gap {
+            SCLogDebug!("TODO TS trying to catch up after GAP (input {})", cur_i.len());
+            match search_smb2_record(cur_i) {
+                IResult::Done(_, pg) => {
+                    SCLogDebug!("smb record found");
+                    let smb2_offset = cur_i.len() - pg.data.len();
+                    if smb2_offset < 4 {
+                        return 0;
+                    }
+                    let nbss_offset = smb2_offset - 4;
+                    cur_i = &cur_i[nbss_offset..];
+
+                    self.ts_gap = false;
+                },
+                _ => {
+                    SCLogDebug!("smb record NOT found");
+                    self.tcp_buffer_ts.extend_from_slice(cur_i);
+                    return 0;
+                },
+            }
+        }
+        while cur_i.len() > 0 { // min record size
+            match parse_nbss_record(cur_i) {
+                IResult::Done(rem, ref nbss_hdr) => {
+                    if nbss_hdr.message_type == NBSS_MSGTYPE_SESSION_MESSAGE {
+                        // we have the full records size worth of data,
+                        // let's parse it
+                        match parse_smb_version(&nbss_hdr.data) {
+                            IResult::Done(_, ref smb) => {
+                                SCLogDebug!("SMB {:?}", smb);
+                                if smb.version == 255u8 { // SMB1
+                                    SCLogDebug!("SMBv1 record");
+                                    match parse_smb_record(&nbss_hdr.data) {
+                                        IResult::Done(_, ref smb_record) => {
+                                            smb1_request_record(self, smb_record);
+                                        },
+                                        _ => {
+                                            self.set_event(SMBEvent::MalformedData);
+                                            return 1;
+                                        },
+                                    }
+                                } else if smb.version == 254u8 { // SMB2
+                                    let mut nbss_data = nbss_hdr.data;
+                                    while nbss_data.len() > 0 {
+                                        SCLogDebug!("SMBv2 record");
+                                        match parse_smb2_request_record(&nbss_data) {
+                                            IResult::Done(nbss_data_rem, ref smb_record) => {
+                                                SCLogDebug!("nbss_data_rem {}", nbss_data_rem.len());
+
+                                                smb2_request_record(self, smb_record);
+                                                nbss_data = nbss_data_rem;
+                                            },
+                                            _ => {
+                                                self.set_event(SMBEvent::MalformedData);
+                                                return 1;
+                                            },
+                                        }
+                                    }
+                                }
+                            },
+                            _ => {
+                                self.set_event(SMBEvent::MalformedData);
+                                return 1;
+                            },
+                        }
+                    } else {
+                        SCLogDebug!("NBSS message {:X}", nbss_hdr.message_type);
+                    }
+                    cur_i = rem;
+                },
+                IResult::Incomplete(_) => {
+                    let consumed = self.parse_tcp_data_ts_partial(cur_i);
+                    cur_i = &cur_i[consumed ..];
+
+                    self.tcp_buffer_ts.extend_from_slice(cur_i);
+                    break;
+                },
+                IResult::Error(_) => {
+                    self.set_event(SMBEvent::MalformedData);
+                    return 1;
+                },
+            }
+        };
+
+        //self.debug_tx_stats();
+        0
+    }
+
+    /// return bytes consumed
+    pub fn parse_tcp_data_tc_partial<'b>(&mut self, input: &'b[u8]) -> usize
+    {
+        SCLogDebug!("incomplete of size {}", input.len());
+        if input.len() < 512 {
+            return 0;
+        }
+
+        match parse_nbss_record_partial(input) {
+            IResult::Done(output, ref nbss_part_hdr) => {
+                SCLogDebug!("parse_nbss_record_partial ok, output len {}", output.len());
+                if nbss_part_hdr.message_type == NBSS_MSGTYPE_SESSION_MESSAGE {
+                    match parse_smb_version(&nbss_part_hdr.data) {
+                        IResult::Done(_, ref smb) => {
+                            SCLogDebug!("SMB {:?}", smb);
+                            if smb.version == 255u8 { // SMB1
+                                SCLogDebug!("SMBv1 record");
+                                match parse_smb_record(&nbss_part_hdr.data) {
+                                    IResult::Done(_, ref r) => {
+                                        SCLogDebug!("SMB1: partial record {}",
+                                                r.command);
+                                        if r.command == SMB1_COMMAND_READ_ANDX {
+                                            let tree_key = SMBCommonHdr::new(SMBHDR_TYPE_SHARE,
+                                                    r.ssn_id as u64, r.tree_id as u32, 0);
+                                            let is_pipe = match self.ssn2tree_map.get(&tree_key) {
+                                                Some(n) => n.is_pipe,
+                                                None => false,
+                                            };
+                                            if is_pipe {
+                                                return 0;
+                                            }
+                                            smb1_read_response_record(self, r);
+                                            let consumed = input.len() - output.len();
+                                            return consumed;
+                                        }
+                                    },
+                                    _ => { },
+                                }
+                            } else if smb.version == 254u8 { // SMB2
+                                SCLogDebug!("SMBv2 record");
+                                match parse_smb2_response_record(&nbss_part_hdr.data) {
+                                    IResult::Done(_, ref smb_record) => {
+                                        SCLogDebug!("SMB2: partial record {}",
+                                                &smb2_command_string(smb_record.command));
+                                        if smb_record.command == SMB2_COMMAND_READ {
+                                            smb2_read_response_record(self, smb_record);
+                                            let consumed = input.len() - output.len();
+                                            return consumed;
+                                        }
+                                    },
+                                    _ => { },
+                                }
+                            }
+                        },
+                        _ => { },
+                    }
+                }
+            },
+            _ => { },
+        }
+
+        return 0;
+    }
+
+    /// Parsing function, handling TCP chunks fragmentation
+    pub fn parse_tcp_data_tc<'b>(&mut self, i: &'b[u8]) -> u32
+    {
+        self.check_gap_resync();
+
+        let mut v : Vec<u8>;
+        // Check if TCP data is being defragmented
+        let tcp_buffer = match self.tcp_buffer_tc.len() {
+            0 => i,
+            _ => {
+                v = self.tcp_buffer_tc.split_off(0);
+                if self.tcp_buffer_tc.len() + i.len() > 100000 {
+                    self.set_event(SMBEvent::RecordOverflow);
+                    return 1;
+                };
+                v.extend_from_slice(i);
+                v.as_slice()
+            },
+        };
+        let mut cur_i = tcp_buffer;
+        SCLogDebug!("cur_i.len {}", cur_i.len());
+        if cur_i.len() > 100000 {
+            self.set_event(SMBEvent::RecordOverflow);
+            return 1;
+        }
+        let consumed = self.handle_skip(STREAM_TOCLIENT, cur_i.len() as u32);
+        if consumed > 0 {
+            if consumed > cur_i.len() as u32 {
+                self.set_event(SMBEvent::InternalError);
+                return 1;
+            }
+            cur_i = &cur_i[consumed as usize..];
+        }
+        // take care of in progress file chunk transfers
+        // and skip buffer beyond it
+        let consumed = self.filetracker_update(STREAM_TOCLIENT, cur_i, 0);
+        if consumed > 0 {
+            if consumed > cur_i.len() as u32 {
+                self.set_event(SMBEvent::InternalError);
+                return 1;
+            }
+            cur_i = &cur_i[consumed as usize..];
+        }
+        // gap
+        if self.tc_gap {
+            SCLogDebug!("TODO TC trying to catch up after GAP (input {})", cur_i.len());
+            match search_smb2_record(cur_i) {
+                IResult::Done(_, pg) => {
+                    SCLogDebug!("smb record found");
+                    let smb2_offset = cur_i.len() - pg.data.len();
+                    if smb2_offset < 4 {
+                        return 0;
+                    }
+                    let nbss_offset = smb2_offset - 4;
+                    cur_i = &cur_i[nbss_offset..];
+
+                    self.tc_gap = false;
+                },
+                _ => {
+                    SCLogDebug!("smb record NOT found");
+                    self.tcp_buffer_tc.extend_from_slice(cur_i);
+                    return 0;
+                },
+            }
+        }
+        while cur_i.len() > 0 { // min record size
+            match parse_nbss_record(cur_i) {
+                IResult::Done(rem, ref nbss_hdr) => {
+                    if nbss_hdr.message_type == NBSS_MSGTYPE_SESSION_MESSAGE {
+                        // we have the full records size worth of data,
+                        // let's parse it
+                        match parse_smb_version(&nbss_hdr.data) {
+                            IResult::Done(_, ref smb) => {
+                                SCLogDebug!("SMB {:?}", smb);
+                                if smb.version == 255u8 { // SMB1
+                                    SCLogDebug!("SMBv1 record");
+                                    match parse_smb_record(&nbss_hdr.data) {
+                                        IResult::Done(_, ref smb_record) => {
+                                            smb1_response_record(self, smb_record);
+                                        },
+                                        _ => {
+                                            self.set_event(SMBEvent::MalformedData);
+                                            return 1;
+                                        },
+                                    }
+                                } else if smb.version == 254u8 { // SMB2
+                                    let mut nbss_data = nbss_hdr.data;
+                                    while nbss_data.len() > 0 {
+                                        SCLogDebug!("SMBv2 record");
+                                        match parse_smb2_response_record(&nbss_data) {
+                                            IResult::Done(nbss_data_rem, ref smb_record) => {
+                                                smb2_response_record(self, smb_record);
+                                                nbss_data = nbss_data_rem;
+                                            },
+                                            _ => {
+                                                self.set_event(SMBEvent::MalformedData);
+                                                return 1;
+                                            },
+                                        }
+                                    }
+                                }
+                            },
+                            IResult::Incomplete(_) => {
+                                // not enough data to contain basic SMB hdr
+                                // TODO event: empty NBSS_MSGTYPE_SESSION_MESSAGE
+                            },
+                            IResult::Error(_) => {
+                                self.set_event(SMBEvent::MalformedData);
+                                return 1;
+                            },
+                        }
+                    } else {
+                        SCLogDebug!("NBSS message {:X}", nbss_hdr.message_type);
+                    }
+                    cur_i = rem;
+                },
+                IResult::Incomplete(_) => {
+                    SCLogDebug!("INCOMPLETE have {}", cur_i.len());
+                    let consumed = self.parse_tcp_data_tc_partial(cur_i);
+                    cur_i = &cur_i[consumed ..];
+
+                    SCLogDebug!("INCOMPLETE have {}", cur_i.len());
+                    self.tcp_buffer_tc.extend_from_slice(cur_i);
+                    break;
+                },
+                IResult::Error(_) => {
+                    self.set_event(SMBEvent::MalformedData);
+                    return 1;
+                },
+            }
+        };
+        0
+    }
+
+    /// handle a gap in the TOSERVER direction
+    /// returns: 0 ok, 1 unrecoverable error
+    pub fn parse_tcp_data_ts_gap(&mut self, gap_size: u32) -> u32 {
+        if self.tcp_buffer_ts.len() > 0 {
+            self.tcp_buffer_ts.clear();
+        }
+        let consumed = self.handle_skip(STREAM_TOSERVER, gap_size);
+        if consumed < gap_size {
+            let new_gap_size = gap_size - consumed;
+            let gap = vec![0; new_gap_size as usize];
+
+            let consumed2 = self.filetracker_update(STREAM_TOSERVER, &gap, new_gap_size);
+            if consumed2 > new_gap_size {
+                SCLogDebug!("consumed more than GAP size: {} > {}", consumed2, new_gap_size);
+                self.set_event(SMBEvent::InternalError);
+                return 1;
+            }
+        }
+        SCLogDebug!("GAP of size {} in toserver direction", gap_size);
+        self.ts_ssn_gap = true;
+        self.ts_gap = true;
+        return 0
+    }
+
+    /// handle a gap in the TOCLIENT direction
+    /// returns: 0 ok, 1 unrecoverable error
+    pub fn parse_tcp_data_tc_gap(&mut self, gap_size: u32) -> u32 {
+        if self.tcp_buffer_tc.len() > 0 {
+            self.tcp_buffer_tc.clear();
+        }
+        let consumed = self.handle_skip(STREAM_TOCLIENT, gap_size);
+        if consumed < gap_size {
+            let new_gap_size = gap_size - consumed;
+            let gap = vec![0; new_gap_size as usize];
+
+            let consumed2 = self.filetracker_update(STREAM_TOCLIENT, &gap, new_gap_size);
+            if consumed2 > new_gap_size {
+                SCLogDebug!("consumed more than GAP size: {} > {}", consumed2, new_gap_size);
+                self.set_event(SMBEvent::InternalError);
+                return 1;
+            }
+        }
+        SCLogDebug!("GAP of size {} in toclient direction", gap_size);
+        self.tc_ssn_gap = true;
+        self.tc_gap = true;
+        return 0
+    }
+
+    pub fn trunc_ts(&mut self) {
+        SCLogDebug!("TRUNC TS");
+        self.ts_trunc = true;
+
+        for tx in &mut self.transactions {
+            if !tx.request_done {
+                SCLogDebug!("TRUNCING TX {} in TOSERVER direction", tx.id);
+                tx.request_done = true;
+            }
+       }
+    }
+    pub fn trunc_tc(&mut self) {
+        SCLogDebug!("TRUNC TC");
+        self.tc_trunc = true;
+
+        for tx in &mut self.transactions {
+            if !tx.response_done {
+                SCLogDebug!("TRUNCING TX {} in TOCLIENT direction", tx.id);
+                tx.response_done = true;
+            }
+        }
+    }
+}
+
+/// Returns *mut SMBState
+#[no_mangle]
+pub extern "C" fn rs_smb_state_new() -> *mut libc::c_void {
+    let state = SMBState::new();
+    let boxed = Box::new(state);
+    SCLogDebug!("allocating state");
+    return unsafe{transmute(boxed)};
+}
+
+/// Params:
+/// - state: *mut SMBState as void pointer
+#[no_mangle]
+pub extern "C" fn rs_smb_state_free(state: *mut libc::c_void) {
+    // Just unbox...
+    SCLogDebug!("freeing state");
+    let mut smb_state: Box<SMBState> = unsafe{transmute(state)};
+    smb_state.free();
+}
+
+/// C binding parse a SMB request. Returns 1 on success, -1 on failure.
+#[no_mangle]
+pub extern "C" fn rs_smb_parse_request_tcp(_flow: *mut Flow,
+                                       state: &mut SMBState,
+                                       _pstate: *mut libc::c_void,
+                                       input: *mut libc::uint8_t,
+                                       input_len: libc::uint32_t,
+                                       _data: *mut libc::c_void)
+                                       -> libc::int8_t
+{
+    let buf = unsafe{std::slice::from_raw_parts(input, input_len as usize)};
+    SCLogDebug!("parsing {} bytes of request data", input_len);
+
+    if state.parse_tcp_data_ts(buf) == 0 {
+        return 1;
+    } else {
+        return -1;
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_smb_parse_request_tcp_gap(
+                                        state: &mut SMBState,
+                                        input_len: libc::uint32_t)
+                                        -> libc::int8_t
+{
+    if state.parse_tcp_data_ts_gap(input_len as u32) == 0 {
+        return 1;
+    }
+    return -1;
+}
+
+
+#[no_mangle]
+pub extern "C" fn rs_smb_parse_response_tcp(_flow: *mut Flow,
+                                        state: &mut SMBState,
+                                        _pstate: *mut libc::c_void,
+                                        input: *mut libc::uint8_t,
+                                        input_len: libc::uint32_t,
+                                        _data: *mut libc::c_void)
+                                        -> libc::int8_t
+{
+    SCLogDebug!("parsing {} bytes of response data", input_len);
+    let buf = unsafe{std::slice::from_raw_parts(input, input_len as usize)};
+
+    if state.parse_tcp_data_tc(buf) == 0 {
+        return 1;
+    } else {
+        return -1;
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_smb_parse_response_tcp_gap(
+                                        state: &mut SMBState,
+                                        input_len: libc::uint32_t)
+                                        -> libc::int8_t
+{
+    if state.parse_tcp_data_tc_gap(input_len as u32) == 0 {
+        return 1;
+    }
+    return -1;
+}
+
+/// TOSERVER probe function
+#[no_mangle]
+pub extern "C" fn rs_smb_probe_tcp_ts(_input: *const libc::uint8_t, _len: libc::uint32_t)
+                               -> libc::int8_t
+{
+//    let slice: &[u8] = unsafe {
+//        std::slice::from_raw_parts(input as *mut u8, len as usize)
+//    };
+    //return smb3_probe(slice, STREAM_TOSERVER);
+    return 1
+}
+/// TOCLIENT probe function
+#[no_mangle]
+pub extern "C" fn rs_smb_probe_tcp_tc(_input: *const libc::uint8_t, _len: libc::uint32_t)
+                               -> libc::int8_t
+{
+//    let slice: &[u8] = unsafe {
+//        std::slice::from_raw_parts(input as *mut u8, len as usize)
+//    };
+    //return smb3_probe(slice, STREAM_TOCLIENT);
+    return 1
+}
+
+#[no_mangle]
+pub extern "C" fn rs_smb_state_get_tx_count(state: &mut SMBState)
+                                            -> libc::uint64_t
+{
+    SCLogDebug!("rs_smb_state_get_tx_count: returning {}", state.tx_id);
+    return state.tx_id;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_smb_state_get_tx(state: &mut SMBState,
+                                      tx_id: libc::uint64_t)
+                                      -> *mut SMBTransaction
+{
+    match state.get_tx_by_id(tx_id) {
+        Some(tx) => {
+            return unsafe{transmute(tx)};
+        }
+        None => {
+            return std::ptr::null_mut();
+        }
+    }
+}
+
+// for use with the C API call StateGetTxIterator
+#[no_mangle]
+pub extern "C" fn rs_smb_state_get_tx_iterator(
+                                      state: &mut SMBState,
+                                      min_tx_id: libc::uint64_t,
+                                      istate: &mut libc::uint64_t)
+                                      -> applayer::AppLayerGetTxIterTuple
+{
+    match state.get_tx_iterator(min_tx_id, istate) {
+        Some((tx, out_tx_id, has_next)) => {
+            let c_tx = unsafe { transmute(tx) };
+            let ires = applayer::AppLayerGetTxIterTuple::with_values(c_tx, out_tx_id, has_next);
+            return ires;
+        }
+        None => {
+            return applayer::AppLayerGetTxIterTuple::not_found();
+        }
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_smb_state_tx_free(state: &mut SMBState,
+                                       tx_id: libc::uint64_t)
+{
+    SCLogDebug!("freeing tx {}", tx_id as u64);
+    state.free_tx(tx_id);
+}
+
+#[no_mangle]
+pub extern "C" fn rs_smb_state_progress_completion_status(
+    _direction: libc::uint8_t)
+    -> libc::c_int
+{
+    return 1;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_smb_tx_get_alstate_progress(tx: &mut SMBTransaction,
+                                                  direction: libc::uint8_t)
+                                                  -> libc::uint8_t
+{
+    if direction == STREAM_TOSERVER && tx.request_done {
+        SCLogDebug!("tx {} TOSERVER progress 1 => {:?}", tx.id, tx);
+        return 1;
+    } else if direction == STREAM_TOCLIENT && tx.response_done {
+        SCLogDebug!("tx {} TOCLIENT progress 1 => {:?}", tx.id, tx);
+        return 1;
+    } else {
+        SCLogDebug!("tx {} direction {} progress 0", tx.id, direction);
+        return 0;
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_smb_tx_set_logged(_state: &mut SMBState,
+                                       tx: &mut SMBTransaction,
+                                       bits: libc::uint32_t)
+{
+    tx.logged.set(bits);
+}
+
+#[no_mangle]
+pub extern "C" fn rs_smb_tx_get_logged(_state: &mut SMBState,
+                                       tx: &mut SMBTransaction)
+                                       -> u32
+{
+    return tx.logged.get();
+}
+
+#[no_mangle]
+pub extern "C" fn rs_smb_tx_set_detect_flags(
+                                       tx: &mut SMBTransaction,
+                                       direction: libc::uint8_t,
+                                       flags: libc::uint64_t)
+{
+    if (direction & STREAM_TOSERVER) != 0 {
+        tx.detect_flags_ts = flags as u64;
+    } else {
+        tx.detect_flags_tc = flags as u64;
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_smb_tx_get_detect_flags(
+                                       tx: &mut SMBTransaction,
+                                       direction: libc::uint8_t)
+                                       -> libc::uint64_t
+{
+    if (direction & STREAM_TOSERVER) != 0 {
+        return tx.detect_flags_ts as libc::uint64_t;
+    } else {
+        return tx.detect_flags_tc as libc::uint64_t;
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_smb_state_set_tx_detect_state(
+    tx: &mut SMBTransaction,
+    de_state: &mut DetectEngineState)
+{
+    tx.de_state = Some(de_state);
+}
+
+#[no_mangle]
+pub extern "C" fn rs_smb_state_get_tx_detect_state(
+    tx: &mut SMBTransaction)
+    -> *mut DetectEngineState
+{
+    match tx.de_state {
+        Some(ds) => {
+            return ds;
+        },
+        None => {
+            return std::ptr::null_mut();
+        }
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_smb_state_truncate(
+        state: &mut SMBState,
+        direction: libc::uint8_t)
+{
+    if (direction & STREAM_TOSERVER) != 0 {
+        state.trunc_ts();
+    } else {
+        state.trunc_tc();
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_smb_state_get_events(state: &mut SMBState,
+                                          tx_id: libc::uint64_t)
+                                          -> *mut AppLayerDecoderEvents
+{
+    match state.get_tx_by_id(tx_id) {
+        Some(tx) => {
+            return tx.events;
+        }
+        _ => {
+            return std::ptr::null_mut();
+        }
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_smb_state_get_event_info(event_name: *const libc::c_char,
+                                              event_id: *mut libc::c_int,
+                                              event_type: *mut AppLayerEventType)
+                                              -> i8
+{
+    if event_name == std::ptr::null() {
+        return -1;
+    }
+    let c_event_name: &CStr = unsafe { CStr::from_ptr(event_name) };
+    let event = match c_event_name.to_str() {
+        Ok(s) => {
+            smb_str_to_event(s)
+        },
+        Err(_) => -1, // UTF-8 conversion failed
+    };
+    unsafe {
+        *event_type = APP_LAYER_EVENT_TYPE_TRANSACTION;
+        *event_id = event as libc::c_int;
+    };
+    if event == -1 {
+        return -1;
+    }
+    0
+}
diff --git a/rust/src/smb/smb1.rs b/rust/src/smb/smb1.rs
new file mode 100644 (file)
index 0000000..c758321
--- /dev/null
@@ -0,0 +1,822 @@
+/* Copyright (C) 2018 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/* TODO
+ * - check all parsers for calls on non-SUCCESS status
+ */
+
+extern crate libc;
+use std::str;
+
+use nom::{IResult};
+
+use core::*;
+use log::*;
+
+use smb::smb1_records::*;
+use smb::smb::*;
+use smb::dcerpc::*;
+use smb::events::*;
+use smb::auth::*;
+use smb::files::*;
+
+// https://msdn.microsoft.com/en-us/library/ee441741.aspx
+pub const SMB1_COMMAND_CREATE_DIRECTORY:        u8 = 0x00;
+pub const SMB1_COMMAND_DELETE_DIRECTORY:        u8 = 0x01;
+pub const SMB1_COMMAND_OPEN:                    u8 = 0x02;
+pub const SMB1_COMMAND_CREATE:                  u8 = 0x03;
+pub const SMB1_COMMAND_CLOSE:                   u8 = 0x04;
+pub const SMB1_COMMAND_FLUSH:                   u8 = 0x05;
+pub const SMB1_COMMAND_DELETE:                  u8 = 0x06;
+pub const SMB1_COMMAND_RENAME:                  u8 = 0x07;
+pub const SMB1_COMMAND_QUERY_INFORMATION:       u8 = 0x08;
+pub const SMB1_COMMAND_SET_INFORMATION:         u8 = 0x09;
+pub const SMB1_COMMAND_READ:                    u8 = 0x0a;
+pub const SMB1_COMMAND_WRITE:                   u8 = 0x0b;
+pub const SMB1_COMMAND_LOCK_BYTE_RANGE:         u8 = 0x0c;
+pub const SMB1_COMMAND_UNLOCK_BYTE_RANGE:       u8 = 0x0d;
+pub const SMB1_COMMAND_CREATE_TEMPORARY:        u8 = 0x0e;
+pub const SMB1_COMMAND_CREATE_NEW:              u8 = 0x0f;
+pub const SMB1_COMMAND_CHECK_DIRECTORY:         u8 = 0x10;
+pub const SMB1_COMMAND_PROCESS_EXIT:            u8 = 0x11;
+pub const SMB1_COMMAND_SEEK:                    u8 = 0x12;
+pub const SMB1_COMMAND_LOCK_AND_READ:           u8 = 0x13;
+pub const SMB1_COMMAND_WRITE_AND_UNLOCK:        u8 = 0x14;
+pub const SMB1_COMMAND_LOCKING_ANDX:            u8 = 0x24;
+pub const SMB1_COMMAND_TRANS:                   u8 = 0x25;
+pub const SMB1_COMMAND_ECHO:                    u8 = 0x2b;
+pub const SMB1_COMMAND_READ_ANDX:               u8 = 0x2e;
+pub const SMB1_COMMAND_WRITE_ANDX:              u8 = 0x2f;
+pub const SMB1_COMMAND_TRANS2:                  u8 = 0x32;
+pub const SMB1_COMMAND_TRANS2_SECONDARY:        u8 = 0x33;
+pub const SMB1_COMMAND_FIND_CLOSE2:             u8 = 0x34;
+pub const SMB1_COMMAND_TREE_DISCONNECT:         u8 = 0x71;
+pub const SMB1_COMMAND_NEGOTIATE_PROTOCOL:      u8 = 0x72;
+pub const SMB1_COMMAND_SESSION_SETUP_ANDX:      u8 = 0x73;
+pub const SMB1_COMMAND_LOGOFF_ANDX:             u8 = 0x74;
+pub const SMB1_COMMAND_TREE_CONNECT_ANDX:       u8 = 0x75;
+pub const SMB1_COMMAND_NT_TRANS:                u8 = 0xa0;
+pub const SMB1_COMMAND_NT_CREATE_ANDX:          u8 = 0xa2;
+pub const SMB1_COMMAND_NT_CANCEL:               u8 = 0xa4;
+
+pub fn smb1_command_string(c: u8) -> String {
+    match c {
+        SMB1_COMMAND_CREATE_DIRECTORY   => "SMB1_COMMAND_CREATE_DIRECTORY",
+        SMB1_COMMAND_DELETE_DIRECTORY   => "SMB1_COMMAND_DELETE_DIRECTORY",
+        SMB1_COMMAND_OPEN               => "SMB1_COMMAND_OPEN",
+        SMB1_COMMAND_CREATE             => "SMB1_COMMAND_CREATE",
+        SMB1_COMMAND_CLOSE              => "SMB1_COMMAND_CLOSE",
+        SMB1_COMMAND_FLUSH              => "SMB1_COMMAND_FLUSH",
+        SMB1_COMMAND_DELETE             => "SMB1_COMMAND_DELETE",
+        SMB1_COMMAND_RENAME             => "SMB1_COMMAND_RENAME",
+        SMB1_COMMAND_QUERY_INFORMATION  => "SMB1_COMMAND_QUERY_INFORMATION",
+        SMB1_COMMAND_SET_INFORMATION    => "SMB1_COMMAND_SET_INFORMATION",
+        SMB1_COMMAND_READ               => "SMB1_COMMAND_READ",
+        SMB1_COMMAND_WRITE              => "SMB1_COMMAND_WRITE",
+        SMB1_COMMAND_LOCK_BYTE_RANGE    => "SMB1_COMMAND_LOCK_BYTE_RANGE",
+        SMB1_COMMAND_UNLOCK_BYTE_RANGE  => "SMB1_COMMAND_UNLOCK_BYTE_RANGE",
+        SMB1_COMMAND_CREATE_TEMPORARY   => "SMB1_COMMAND_CREATE_TEMPORARY",
+        SMB1_COMMAND_CREATE_NEW         => "SMB1_COMMAND_CREATE_NEW",
+        SMB1_COMMAND_CHECK_DIRECTORY    => "SMB1_COMMAND_CHECK_DIRECTORY",
+        SMB1_COMMAND_PROCESS_EXIT       => "SMB1_COMMAND_PROCESS_EXIT",
+        SMB1_COMMAND_SEEK               => "SMB1_COMMAND_SEEK",
+        SMB1_COMMAND_LOCK_AND_READ      => "SMB1_COMMAND_LOCK_AND_READ",
+        SMB1_COMMAND_WRITE_AND_UNLOCK   => "SMB1_COMMAND_WRITE_AND_UNLOCK",
+        SMB1_COMMAND_LOCKING_ANDX       => "SMB1_COMMAND_LOCKING_ANDX",
+        SMB1_COMMAND_ECHO               => "SMB1_COMMAND_ECHO",
+        SMB1_COMMAND_READ_ANDX          => "SMB1_COMMAND_READ_ANDX",
+        SMB1_COMMAND_WRITE_ANDX         => "SMB1_COMMAND_WRITE_ANDX",
+        SMB1_COMMAND_TRANS              => "SMB1_COMMAND_TRANS",
+        SMB1_COMMAND_TRANS2             => "SMB1_COMMAND_TRANS2",
+        SMB1_COMMAND_TRANS2_SECONDARY   => "SMB1_COMMAND_TRANS2_SECONDARY",
+        SMB1_COMMAND_FIND_CLOSE2        => "SMB1_COMMAND_FIND_CLOSE2",
+        SMB1_COMMAND_TREE_DISCONNECT    => "SMB1_COMMAND_TREE_DISCONNECT",
+        SMB1_COMMAND_NEGOTIATE_PROTOCOL => "SMB1_COMMAND_NEGOTIATE_PROTOCOL",
+        SMB1_COMMAND_SESSION_SETUP_ANDX => "SMB1_COMMAND_SESSION_SETUP_ANDX",
+        SMB1_COMMAND_LOGOFF_ANDX        => "SMB1_COMMAND_LOGOFF_ANDX",
+        SMB1_COMMAND_TREE_CONNECT_ANDX  => "SMB1_COMMAND_TREE_CONNECT_ANDX",
+        SMB1_COMMAND_NT_TRANS           => "SMB1_COMMAND_NT_TRANS",
+        SMB1_COMMAND_NT_CREATE_ANDX     => "SMB1_COMMAND_NT_CREATE_ANDX",
+        SMB1_COMMAND_NT_CANCEL          => "SMB1_COMMAND_NT_CANCEL",
+        _ => { return (c).to_string(); },
+    }.to_string()
+}
+
+// later we'll use this to determine if we need to
+// track a ssn per type
+pub fn smb1_create_new_tx(_cmd: u8) -> bool {
+//    if _cmd == SMB1_COMMAND_READ_ANDX {
+//        false
+//    } else {
+        true
+//    }
+}
+
+pub fn smb1_request_record<'b>(state: &mut SMBState, r: &SmbRecord<'b>) -> u32 {
+    SCLogDebug!("record: {:?} command {}", r.greeter, r.command);
+
+    // init default tx keys
+    let mut key_ssn_id = r.ssn_id;
+    let key_tree_id = r.tree_id;
+    let key_multiplex_id = r.multiplex_id;
+    let mut events : Vec<SMBEvent> = Vec::new();
+    let mut no_response_expected = false;
+
+    let have_tx = match r.command {
+        SMB1_COMMAND_READ_ANDX => {
+            match parse_smb_read_andx_request_record(r.data) {
+                IResult::Done(_, rr) => {
+                    SCLogDebug!("rr {:?}", rr);
+
+                    // store read fid,offset in map
+                    let fid_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_OFFSET);
+                    let mut fid = rr.fid.to_vec();
+                    fid.extend_from_slice(&u32_as_bytes(r.ssn_id));
+                    let fidoff = SMBFileGUIDOffset::new(fid, rr.offset);
+                    state.ssn2vecoffset_map.insert(fid_key, fidoff);
+                },
+                _ => {
+                    events.push(SMBEvent::MalformedData);
+                },
+            }
+            false
+        },
+        SMB1_COMMAND_WRITE_ANDX => {
+            smb1_write_request_record(state, r);
+            true // tx handling in func
+        },
+        SMB1_COMMAND_WRITE => {
+            smb1_write_request_record(state, r);
+            true // tx handling in func
+        },
+        SMB1_COMMAND_TRANS => {
+            smb1_trans_request_record(state, r);
+            true
+        },
+        SMB1_COMMAND_NEGOTIATE_PROTOCOL => {
+            match parse_smb1_negotiate_protocol_record(r.data) {
+                IResult::Done(_, pr) => {
+                    SCLogDebug!("SMB_COMMAND_NEGOTIATE_PROTOCOL {:?}", pr);
+
+                    let mut dialects : Vec<Vec<u8>> = Vec::new();
+                    for d in &pr.dialects {
+                        let x = &d[1..d.len()];
+                        let dvec = x.to_vec();
+                        dialects.push(dvec);
+                    }
+
+                    let found = match state.get_negotiate_tx(1) {
+                        Some(tx) => {
+                            SCLogDebug!("WEIRD, should not have NEGOTIATE tx!");
+                            tx.set_event(SMBEvent::DuplicateNegotiate);
+                            true
+                        },
+                        None => { false },
+                    };
+                    if !found {
+                        let tx = state.new_negotiate_tx(1);
+                        if let Some(SMBTransactionTypeData::NEGOTIATE(ref mut tdn)) = tx.type_data {
+                            tdn.dialects = dialects;
+                        }
+                        tx.request_done = true;
+                    }
+                    true
+                },
+                _ => {
+                    events.push(SMBEvent::MalformedData);
+                    false
+                },
+            }
+        },
+        SMB1_COMMAND_NT_CREATE_ANDX => {
+            match parse_smb_create_andx_request_record(r.data) {
+                IResult::Done(_, cr) => {
+                    SCLogDebug!("Create AndX {:?}", cr);
+                    let del = cr.create_options & 0x0000_1000 != 0;
+                    let dir = cr.create_options & 0x0000_0001 != 0;
+                    SCLogDebug!("del {} dir {} options {:08x}", del, dir, cr.create_options);
+
+                    let name_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_FILENAME);
+                    let name_val = cr.file_name.to_vec();
+                    state.ssn2vec_map.insert(name_key, name_val);
+
+                    let tx_hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_GENERICTX);
+                    let tx = state.new_create_tx(&cr.file_name.to_vec(),
+                            cr.disposition, del, dir, tx_hdr);
+                    tx.vercmd.set_smb1_cmd(r.command);
+                    SCLogDebug!("TS CREATE TX {} created", tx.id);
+                    true
+                },
+                _ => {
+                    events.push(SMBEvent::MalformedData);
+                    false
+                },
+            }
+        },
+        SMB1_COMMAND_SESSION_SETUP_ANDX => {
+            SCLogDebug!("SMB1_COMMAND_SESSION_SETUP_ANDX user_id {}", r.user_id);
+            match parse_smb_setup_andx_record(r.data) {
+                IResult::Done(_, setup) => {
+                    parse_secblob(state, setup.sec_blob);
+/*
+                        _ => {
+                            events.push(SMBEvent::MalformedNtlmsspRequest);
+                        },
+*/
+                },
+                _ => {
+                    events.push(SMBEvent::MalformedData);
+                },
+            }
+            key_ssn_id = 0;
+            false
+        },
+        SMB1_COMMAND_TREE_CONNECT_ANDX => {
+            SCLogDebug!("SMB1_COMMAND_TREE_CONNECT_ANDX");
+            match parse_smb_connect_tree_andx_record(r.data) {
+                IResult::Done(_, create_record) => {
+                    let name_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_TREE);
+                    let mut name_val = create_record.share.to_vec();
+                    name_val.retain(|&i|i != 0x00);
+                    if name_val.len() > 1 {
+                        name_val = name_val[1..].to_vec();
+                    }
+                    //state.ssn2vec_map.insert(name_key, name_val);
+
+                    // store hdr as SMBHDR_TYPE_TREE, so with tree id 0
+                    // when the response finds this we update it
+                    let tx = state.new_treeconnect_tx(name_key, name_val);
+                    tx.request_done = true;
+                    tx.vercmd.set_smb1_cmd(SMB1_COMMAND_TREE_CONNECT_ANDX);
+                    true
+                },
+                _ => {
+                    events.push(SMBEvent::MalformedData);
+                    false
+                },
+            }
+        },
+        SMB1_COMMAND_TREE_DISCONNECT => {
+            let tree_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_SHARE);
+            state.ssn2tree_map.remove(&tree_key);
+            false
+        },
+        SMB1_COMMAND_CLOSE => {
+            match parse_smb1_close_request_record(r.data) {
+                IResult::Done(_, cd) => {
+                    let mut fid = cd.fid.to_vec();
+                    fid.extend_from_slice(&u32_as_bytes(r.ssn_id));
+                    SCLogDebug!("closing FID {:?}/{:?}", cd.fid, fid);
+
+                    // we can have created 2 txs for a FID: one for reads
+                    // and one for writes. So close both.
+                    match state.get_file_tx_by_guid(&fid, STREAM_TOSERVER) {
+                        Some((tx, files, flags)) => {
+                            SCLogDebug!("found tx {}", tx.id);
+                            if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+                                if !tx.request_done {
+                                    SCLogDebug!("closing file tx {} FID {:?}/{:?}", tx.id, cd.fid, fid);
+                                    tdf.file_tracker.close(files, flags);
+                                    tx.request_done = true;
+                                    tx.response_done = true;
+                                    SCLogDebug!("tx {} is done", tx.id);
+                                }
+                                // as a precaution, reset guid so it can be reused
+                                tdf.guid.clear(); // TODO review
+                            }
+                        },
+                        None => { },
+                    }
+                    match state.get_file_tx_by_guid(&fid, STREAM_TOCLIENT) {
+                        Some((tx, files, flags)) => {
+                            SCLogDebug!("found tx {}", tx.id);
+                            if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+                                if !tx.request_done {
+                                    SCLogDebug!("closing file tx {} FID {:?}/{:?}", tx.id, cd.fid, fid);
+                                    tdf.file_tracker.close(files, flags);
+                                    tx.request_done = true;
+                                    tx.response_done = true;
+                                    SCLogDebug!("tx {} is done", tx.id);
+                                }
+                                // as a precaution, reset guid so it can be reused
+                                tdf.guid.clear(); // TODO review now that fid is improved
+                            }
+                        },
+                        None => { },
+                    }
+                },
+                _ => {
+                    events.push(SMBEvent::MalformedData);
+                },
+            }
+            false
+        },
+        SMB1_COMMAND_NT_CANCEL => {
+            no_response_expected = true;
+            false
+        },
+        SMB1_COMMAND_TRANS2_SECONDARY => {
+            no_response_expected = true;
+            false
+        },
+        _ => {
+            if r.command == SMB1_COMMAND_TRANS2 ||
+               r.command == SMB1_COMMAND_LOGOFF_ANDX ||
+               r.command == SMB1_COMMAND_TREE_DISCONNECT ||
+               r.command == SMB1_COMMAND_NT_TRANS ||
+               r.command == SMB1_COMMAND_NT_CANCEL ||
+               r.command == SMB1_COMMAND_RENAME ||
+               r.command == SMB1_COMMAND_CHECK_DIRECTORY ||
+               r.command == SMB1_COMMAND_LOCKING_ANDX ||
+               r.command == SMB1_COMMAND_ECHO ||
+               r.command == SMB1_COMMAND_TRANS
+            { } else {
+                 SCLogDebug!("unsupported command {}/{}",
+                         r.command, &smb1_command_string(r.command));
+            }
+            false
+        },
+    };
+    if !have_tx {
+        if smb1_create_new_tx(r.command) {
+            let tx_key = SMBCommonHdr::new(SMBHDR_TYPE_GENERICTX,
+                    key_ssn_id as u64, key_tree_id as u32, key_multiplex_id as u64);
+            let tx = state.new_generic_tx(1, r.command as u16, tx_key);
+            SCLogDebug!("tx {} created for {}/{}", tx.id, r.command, &smb1_command_string(r.command));
+            tx.set_events(events);
+            if no_response_expected {
+                tx.response_done = true;
+            }
+        }
+    }
+    0
+}
+
+pub fn smb1_response_record<'b>(state: &mut SMBState, r: &SmbRecord<'b>) -> u32 {
+    SCLogDebug!("record: {:?} command {}", r.greeter, r.command);
+
+    let key_ssn_id = r.ssn_id;
+    let key_tree_id = r.tree_id;
+    let key_multiplex_id = r.multiplex_id;
+    let mut tx_sync = false;
+    let mut events : Vec<SMBEvent> = Vec::new();
+
+    let have_tx = match r.command {
+        SMB1_COMMAND_READ_ANDX => {
+            smb1_read_response_record(state, &r);
+            true // tx handling in func
+        },
+        SMB1_COMMAND_NEGOTIATE_PROTOCOL => {
+            SCLogDebug!("SMB1_COMMAND_NEGOTIATE_PROTOCOL response");
+            match parse_smb1_negotiate_protocol_response_record(r.data) {
+                IResult::Done(_, pr) => {
+                    let (have_ntx, dialect) = match state.get_negotiate_tx(1) {
+                        Some(tx) => {
+                            tx.set_status(r.nt_status, r.is_dos_error);
+                            tx.response_done = true;
+                            SCLogDebug!("tx {} is done", tx.id);
+                            let d = match tx.type_data {
+                                Some(SMBTransactionTypeData::NEGOTIATE(ref mut x)) => {
+                                    let dialect_idx = pr.dialect_idx as usize;
+                                    if x.dialects.len() <= dialect_idx {
+                                        None
+                                    } else {
+                                        let d = x.dialects[dialect_idx].to_vec();
+                                        Some(d)
+                                    }
+                                },
+                                _ => { None },
+                            };
+                            if d == None {
+                                tx.set_event(SMBEvent::MalformedData);
+                            }
+                            (true, d)
+                        },
+                        None => { (false, None) },
+                    };
+                    match dialect {
+                        Some(d) => {
+                            SCLogDebug!("dialect {:?}", d);
+                            state.dialect_vec = Some(d);
+                        },
+                        _ => { },
+                    }
+                    have_ntx
+                },
+                _ => {
+                    events.push(SMBEvent::MalformedData);
+                    false
+                },
+            }
+        },
+        SMB1_COMMAND_TREE_CONNECT_ANDX => {
+            if r.nt_status != SMB_NTSTATUS_SUCCESS {
+                let name_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_TREE);
+                match state.get_treeconnect_tx(name_key) {
+                    Some(tx) => {
+                        if let Some(SMBTransactionTypeData::TREECONNECT(ref mut tdn)) = tx.type_data {
+                            tdn.tree_id = r.tree_id as u32;
+                        }
+                        tx.set_status(r.nt_status, r.is_dos_error);
+                        tx.response_done = true;
+                        SCLogDebug!("tx {} is done", tx.id);
+                    },
+                    None => { },
+                }
+                return 0;
+            }
+
+            match parse_smb_connect_tree_andx_response_record(r.data) {
+                IResult::Done(_, tr) => {
+                    let name_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_TREE);
+                    let is_pipe = tr.service == "IPC".as_bytes();
+                    let mut share_name = Vec::new();
+                    let found = match state.get_treeconnect_tx(name_key) {
+                        Some(tx) => {
+                            if let Some(SMBTransactionTypeData::TREECONNECT(ref mut tdn)) = tx.type_data {
+                                tdn.is_pipe = is_pipe;
+                                tdn.tree_id = r.tree_id as u32;
+                                share_name = tdn.share_name.to_vec();
+                            }
+                            tx.hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_HEADER);
+                            tx.set_status(r.nt_status, r.is_dos_error);
+                            tx.response_done = true;
+                            SCLogDebug!("tx {} is done", tx.id);
+                            true
+                        },
+                        None => { false },
+                    };
+                    if found {
+                        let tree = SMBTree::new(share_name.to_vec(), is_pipe);
+                        let tree_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_SHARE);
+                        state.ssn2tree_map.insert(tree_key, tree);
+                    }
+                    found
+                },
+                _ => {
+                    events.push(SMBEvent::MalformedData);
+                    false
+                },
+            }
+        },
+        SMB1_COMMAND_TREE_DISCONNECT => {
+            // normally removed when processing request,
+            // but in case we missed that try again here
+            let tree_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_SHARE);
+            state.ssn2tree_map.remove(&tree_key);
+            false
+        },
+        SMB1_COMMAND_NT_CREATE_ANDX => {
+            match parse_smb_create_andx_response_record(r.data) {
+                IResult::Done(_, cr) => {
+                    SCLogDebug!("Create AndX {:?}", cr);
+
+                    let guid_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_FILENAME);
+                    match state.ssn2vec_map.remove(&guid_key) {
+                        Some(mut p) => {
+                            p.retain(|&i|i != 0x00);
+
+                            let mut fid = cr.fid.to_vec();
+                            fid.extend_from_slice(&u32_as_bytes(r.ssn_id));
+                            SCLogDebug!("SMB1_COMMAND_NT_CREATE_ANDX fid {:?}", fid);
+                            SCLogDebug!("fid {:?} name {:?}", fid, p);
+                            state.guid2name_map.insert(fid, p);
+                        },
+                        _ => {
+                            SCLogDebug!("SMBv1 response: GUID NOT FOUND");
+                        },
+                    }
+                },
+                _ => { events.push(SMBEvent::MalformedData); },
+            }
+            false
+        },
+        SMB1_COMMAND_TRANS => {
+            smb1_trans_response_record(state, r);
+            true
+        },
+        SMB1_COMMAND_SESSION_SETUP_ANDX => {
+            tx_sync = true;
+            false
+        },
+        SMB1_COMMAND_LOGOFF_ANDX => {
+            tx_sync = true;
+            false
+        },
+        _ => {
+            false
+        },
+    };
+
+    if !have_tx && tx_sync {
+        match state.get_last_tx(1, r.command as u16) {
+            Some(tx) => {
+                SCLogDebug!("last TX {} is CMD {}", tx.id, &smb1_command_string(r.command));
+                tx.response_done = true;
+                SCLogDebug!("tx {} cmd {} is done", tx.id, r.command);
+                tx.set_status(r.nt_status, r.is_dos_error);
+                tx.set_events(events);
+            },
+            _ => {},
+        }
+    } else if !have_tx && smb1_create_new_tx(r.command) {
+        let tx_key = SMBCommonHdr::new(SMBHDR_TYPE_GENERICTX,
+                key_ssn_id as u64, key_tree_id as u32, key_multiplex_id as u64);
+        let _have_tx2 = match state.get_generic_tx(1, r.command as u16, &tx_key) {
+            Some(tx) => {
+                tx.request_done = true;
+                tx.response_done = true;
+                SCLogDebug!("tx {} cmd {} is done", tx.id, r.command);
+                tx.set_status(r.nt_status, r.is_dos_error);
+                tx.set_events(events);
+                true
+            },
+            _ => {
+                SCLogDebug!("no TX found for key {:?}", tx_key);
+                false
+            },
+        };
+    } else {
+        SCLogDebug!("have tx for cmd {}", r.command);
+    }
+    0
+}
+
+pub fn get_service_for_nameslice(nameslice: &[u8]) -> (&'static str, bool)
+{
+    let mut name = nameslice.to_vec();
+    name.retain(|&i|i != 0x00);
+
+    match str::from_utf8(&name) {
+        Ok("\\PIPE\\LANMAN") => ("LANMAN", false),
+        Ok("\\PIPE\\") => ("PIPE", true), // TODO not sure if this is true
+        Err(_) => ("MALFORMED", false),
+        Ok(&_) => {
+            SCLogNotice!("don't know \"{}\"", String::from_utf8_lossy(&name));
+            ("UNKNOWN", false)
+        },
+    }
+}
+
+pub fn smb1_trans_request_record<'b>(state: &mut SMBState, r: &SmbRecord<'b>)
+{
+    match parse_smb_trans_request_record(r.data, r) {
+        IResult::Done(_, rd) => {
+            SCLogDebug!("TRANS request {:?}", rd);
+
+            /* if we have a fid, store it so the response can pick it up */
+            if rd.pipe != None {
+                let pipe = rd.pipe.unwrap();
+                state.ssn2vec_map.insert(SMBCommonHdr::from1(r, SMBHDR_TYPE_GUID),
+                        pipe.fid.to_vec());
+            }
+
+            let (sername, is_dcerpc) = get_service_for_nameslice(&rd.txname.name);
+            SCLogDebug!("service: {} dcerpc {}", sername, is_dcerpc);
+            if is_dcerpc {
+                // store tx name so the response also knows this is dcerpc
+                let txn_hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_TXNAME);
+                state.ssn2vec_map.insert(txn_hdr, rd.txname.name.to_vec());
+
+                // trans request will tell us the max size of the response
+                // if there is more response data, it will first give a
+                // TRANS with 'max data cnt' worth of data, and the rest
+                // will be pulled by a 'READ'. So we setup an expectation
+                // here.
+                if rd.params.max_data_cnt > 0 {
+                    // expect max max_data_cnt for this fid in the other dir
+                    let ehdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_MAX_SIZE);
+                    state.ssn2maxsize_map.insert(ehdr, rd.params.max_data_cnt);
+                }
+
+                SCLogDebug!("SMBv1 TRANS TO PIPE");
+                let hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_HEADER);
+                let vercmd = SMBVerCmdStat::new1(r.command);
+                smb_write_dcerpc_record(state, vercmd, hdr, &rd.data.data);
+            }
+        },
+        _ => {
+            state.set_event(SMBEvent::MalformedData);
+        },
+    }
+}
+
+pub fn smb1_trans_response_record<'b>(state: &mut SMBState, r: &SmbRecord<'b>)
+{
+    match parse_smb_trans_response_record(r.data) {
+        IResult::Done(_, rd) => {
+            SCLogDebug!("TRANS response {:?}", rd);
+
+            // see if we have a stored fid
+            let fid = match state.ssn2vec_map.remove(
+                    &SMBCommonHdr::from1(r, SMBHDR_TYPE_GUID)) {
+                Some(f) => f,
+                None => Vec::new(),
+            };
+            SCLogDebug!("FID {:?}", fid);
+
+            // if we get status 'BUFFER_OVERFLOW' this is only a part of
+            // the data. Store it in the ssn/tree for later use.
+            if r.nt_status == SMB_NTSTATUS_BUFFER_OVERFLOW {
+                state.ssnguid2vec_map.insert(SMBHashKeyHdrGuid::new(
+                            SMBCommonHdr::from1(r, SMBHDR_TYPE_TRANS_FRAG), fid),
+                            rd.data.to_vec());
+            } else {
+                let txn_hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_TXNAME);
+                let is_dcerpc = match state.ssn2vec_map.remove(&txn_hdr) {
+                    None => false,
+                    Some(s) => {
+                        let (sername, is_dcerpc) = get_service_for_nameslice(&s);
+                        SCLogDebug!("service: {} dcerpc {}", sername, is_dcerpc);
+                        is_dcerpc
+                    },
+                };
+                if is_dcerpc {
+                    SCLogDebug!("SMBv1 TRANS TO PIPE");
+                    let hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_HEADER);
+                    let vercmd = SMBVerCmdStat::new1_with_ntstatus(r.command, r.nt_status);
+                    smb_read_dcerpc_record(state, vercmd, hdr, &fid, &rd.data);
+                }
+            }
+        },
+        _ => {
+            state.set_event(SMBEvent::MalformedData);
+        },
+    }
+}
+
+/// Handle WRITE and WRITE_ANDX request records
+pub fn smb1_write_request_record<'b>(state: &mut SMBState, r: &SmbRecord<'b>)
+{
+    let result = if r.command == SMB1_COMMAND_WRITE_ANDX {
+        parse_smb1_write_andx_request_record(r.data)
+    } else {
+        parse_smb1_write_request_record(r.data)
+    };
+    match result {
+        IResult::Done(_, rd) => {
+            SCLogDebug!("SMBv1: write andx => {:?}", rd);
+
+            let mut file_fid = rd.fid.to_vec();
+            file_fid.extend_from_slice(&u32_as_bytes(r.ssn_id));
+            SCLogDebug!("SMBv1 WRITE: FID {:?} offset {}",
+                    file_fid, rd.offset);
+
+            let file_name = match state.guid2name_map.get(&file_fid) {
+                Some(n) => n.to_vec(),
+                None => Vec::new(),
+            };
+            let found = match state.get_file_tx_by_guid(&file_fid, STREAM_TOSERVER) {
+                Some((tx, files, flags)) => {
+                    let file_id : u32 = tx.id as u32;
+                    if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+                        filetracker_newchunk(&mut tdf.file_tracker, files, flags,
+                                &file_name, rd.data, rd.offset,
+                                rd.len, 0, false, &file_id);
+                        SCLogDebug!("FID {:?} found at tx {}", file_fid, tx.id);
+                    }
+                    true
+                },
+                None => { false },
+            };
+            if !found {
+                let tree_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_SHARE);
+                let (share_name, is_pipe) = match state.ssn2tree_map.get(&tree_key) {
+                    Some(n) => (n.name.to_vec(), n.is_pipe),
+                    None => (Vec::new(), false),
+                };
+                if is_pipe {
+                    SCLogDebug!("SMBv1 WRITE TO PIPE");
+                    let hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_HEADER);
+                    let vercmd = SMBVerCmdStat::new1_with_ntstatus(r.command, r.nt_status);
+                    smb_write_dcerpc_record(state, vercmd, hdr, &rd.data);
+                } else {
+                    let (tx, files, flags) = state.new_file_tx(&file_fid, &file_name, STREAM_TOSERVER);
+                    if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+                        let file_id : u32 = tx.id as u32;
+                        SCLogDebug!("FID {:?} found at tx {}", file_fid, tx.id);
+                        filetracker_newchunk(&mut tdf.file_tracker, files, flags,
+                                &file_name, rd.data, rd.offset,
+                                rd.len, 0, false, &file_id);
+                        tdf.share_name = share_name;
+                    }
+                    tx.vercmd.set_smb1_cmd(SMB1_COMMAND_WRITE_ANDX);
+                }
+            }
+            state.file_ts_left = rd.len - rd.data.len() as u32;
+            state.file_ts_guid = file_fid.to_vec();
+            SCLogDebug!("SMBv1 WRITE RESPONSE: {} bytes left", state.file_tc_left);
+        },
+        _ => {
+            state.set_event(SMBEvent::MalformedData);
+        },
+    }
+}
+
+fn smb1_read_response_record_generic<'b>(state: &mut SMBState, r: &SmbRecord<'b>) {
+    // see if we want a tx per READ command
+    if smb1_create_new_tx(r.command) {
+        let tx_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_GENERICTX);
+        let tx = state.get_generic_tx(1, r.command as u16, &tx_key);
+        if let Some(tx) = tx {
+            tx.request_done = true;
+            tx.response_done = true;
+            SCLogDebug!("tx {} cmd {} is done", tx.id, r.command);
+            tx.set_status(r.nt_status, r.is_dos_error);
+        }
+    }
+}
+
+pub fn smb1_read_response_record<'b>(state: &mut SMBState, r: &SmbRecord<'b>)
+{
+    smb1_read_response_record_generic(state, r);
+
+    if r.nt_status != SMB_NTSTATUS_SUCCESS {
+        return;
+    }
+
+    match parse_smb_read_andx_response_record(r.data) {
+        IResult::Done(_, rd) => {
+            SCLogDebug!("SMBv1: read response => {:?}", rd);
+
+            let fid_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_OFFSET);
+            let (offset, file_fid) = match state.ssn2vecoffset_map.remove(&fid_key) {
+                Some(o) => (o.offset, o.guid),
+                None => {
+                    SCLogNotice!("SMBv1 READ response: reply to unknown request: left {} {:?}",
+                            rd.len - rd.data.len() as u32, rd);
+                    state.skip_tc = rd.len - rd.data.len() as u32;
+                    return;
+                },
+            };
+            SCLogDebug!("SMBv1 READ: FID {:?} offset {}", file_fid, offset);
+
+            let tree_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_SHARE);
+            let (is_pipe, share_name) = match state.ssn2tree_map.get(&tree_key) {
+                Some(n) => (n.is_pipe, n.name.to_vec()),
+                _ => { (false, Vec::new()) },
+            };
+            if !is_pipe {
+                let file_name = match state.guid2name_map.get(&file_fid) {
+                    Some(n) => n.to_vec(),
+                    None => Vec::new(),
+                };
+                let found = match state.get_file_tx_by_guid(&file_fid, STREAM_TOCLIENT) {
+                    Some((tx, files, flags)) => {
+                        if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+                            let file_id : u32 = tx.id as u32;
+                            SCLogDebug!("FID {:?} found at tx {}", file_fid, tx.id);
+                            filetracker_newchunk(&mut tdf.file_tracker, files, flags,
+                                    &file_name, rd.data, offset,
+                                    rd.len, 0, false, &file_id);
+                        }
+                        true
+                    },
+                    None => { false },
+                };
+                if !found {
+                    let (tx, files, flags) = state.new_file_tx(&file_fid, &file_name, STREAM_TOCLIENT);
+                    if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+                        let file_id : u32 = tx.id as u32;
+                        SCLogDebug!("FID {:?} found at tx {}", file_fid, tx.id);
+                        filetracker_newchunk(&mut tdf.file_tracker, files, flags,
+                                &file_name, rd.data, offset,
+                                rd.len, 0, false, &file_id);
+                        tdf.share_name = share_name;
+                    }
+                    tx.vercmd.set_smb1_cmd(SMB1_COMMAND_READ_ANDX);
+                }
+            } else {
+                SCLogDebug!("SMBv1 READ response from PIPE");
+                let hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_HEADER);
+                let vercmd = SMBVerCmdStat::new1(r.command);
+
+                // hack: we store fid with ssn id mixed in, but here we want the
+                // real thing instead.
+                let pure_fid = if file_fid.len() > 2 { &file_fid[0..2] } else { &[] };
+                smb_read_dcerpc_record(state, vercmd, hdr, &pure_fid, &rd.data);
+            }
+
+            state.file_tc_left = rd.len - rd.data.len() as u32;
+            state.file_tc_guid = file_fid.to_vec();
+            SCLogDebug!("SMBv1 READ RESPONSE: {} bytes left", state.file_tc_left);
+        }
+        _ => {
+            state.set_event(SMBEvent::MalformedData);
+        },
+    }
+}
diff --git a/rust/src/smb/smb1_records.rs b/rust/src/smb/smb1_records.rs
new file mode 100644 (file)
index 0000000..b65fa0a
--- /dev/null
@@ -0,0 +1,599 @@
+/* Copyright (C) 2017 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use log::*;
+use nom::{rest, le_u8, le_u16, le_u32, le_u64, IResult};
+
+#[derive(Debug,PartialEq)]
+pub struct Smb1WriteRequestRecord<'a> {
+    pub offset: u64,
+    pub len: u32,
+    pub fid: &'a[u8],
+    pub data: &'a[u8],
+}
+
+named!(pub parse_smb1_write_request_record<Smb1WriteRequestRecord>,
+    do_parse!(
+            wct: le_u8
+        >>  fid: take!(2)
+        >>  count: le_u16
+        >>  offset: le_u32
+        >>  remaining: le_u16
+        >>  bcc: le_u16
+        >>  buffer_format: le_u8
+        >>  data_len: le_u16
+        >>  file_data: take!(data_len)
+        >> (Smb1WriteRequestRecord {
+                offset: offset as u64,
+                len: data_len as u32,
+                fid:fid,
+                data:file_data,
+            }))
+);
+
+named!(pub parse_smb1_write_andx_request_record<Smb1WriteRequestRecord>,
+    do_parse!(
+            wct: le_u8
+        >>  andx_command: le_u8
+        >>  take!(1)    // reserved
+        >>  andx_offset: le_u16
+        >>  fid: take!(2)
+        >>  offset: le_u32
+        >>  take!(4)    // reserved
+        >>  write_mode: le_u16
+        >>  remaining: le_u16
+        >>  data_len_high: le_u16
+        >>  data_len_low: le_u16
+        >>  data_offset: le_u16
+        >>  high_offset: cond!(wct==14,le_u32)
+        >>  bcc: le_u16
+        //>>  padding: cond!(data_offset > 32, take!(data_offset - 32))
+        >>  padding: cond!(bcc > data_len_low, take!(bcc - data_len_low)) // TODO figure out how this works with data_len_high
+        >>  file_data: rest
+        >> (Smb1WriteRequestRecord {
+                offset: if high_offset != None { ((high_offset.unwrap() as u64) << 32)|(offset as u64) } else { 0 },
+                len: (((data_len_high as u32) << 16) as u32)|(data_len_low as u32),
+                fid:fid,
+                data:file_data,
+            }))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct Smb1NegotiateProtocolResponseRecord<> {
+    pub dialect_idx: u16,
+}
+
+named!(pub parse_smb1_negotiate_protocol_response_record<Smb1NegotiateProtocolResponseRecord>,
+    do_parse!(
+            le_u8
+        >>  dialect_idx: le_u16
+        >> (Smb1NegotiateProtocolResponseRecord {
+                dialect_idx:dialect_idx,
+            }))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct Smb1NegotiateProtocolRecord<'a> {
+    pub dialects: Vec<&'a [u8]>,
+}
+
+named!(pub parse_smb1_negotiate_protocol_record<Smb1NegotiateProtocolRecord>,
+    do_parse!(
+        wtc: le_u8
+        >> bcc: le_u16
+        // dialects is a list of [1 byte buffer format][string][0 terminator]
+        >> dialects: many1!(take_until_and_consume!("\0"))
+        >> (Smb1NegotiateProtocolRecord {
+                dialects:dialects,
+            }))
+);
+
+
+#[derive(Debug,PartialEq)]
+pub struct Smb1ResponseRecordTreeConnectAndX<'a> {
+    pub service: &'a[u8],
+    pub nativefs: &'a[u8],
+}
+
+named!(pub parse_smb_connect_tree_andx_response_record<Smb1ResponseRecordTreeConnectAndX>,
+    do_parse!(
+            wct: le_u8
+        >>  andx_command: le_u8
+        >>  take!(1)    // reserved
+        >>  andx_offset: le_u16
+        >>  cond!(wct >= 3, take!(2))   // optional support
+        >>  cond!(wct == 7, take!(8))   // access masks
+        >>  bcc: le_u16
+        >>  service: take_until_and_consume!("\x00")
+        >>  nativefs: rest
+        >> (Smb1ResponseRecordTreeConnectAndX {
+                service:service,
+                nativefs:nativefs,
+           }))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct SmbRecordTreeConnectAndX<'a> {
+    pub len: usize,
+    pub share: &'a[u8],
+}
+
+named!(pub parse_smb_connect_tree_andx_record<SmbRecordTreeConnectAndX>,
+    do_parse!(
+       skip1: take!(7)
+       >> pwlen: le_u16
+       >> bcc: le_u16
+       >> pw: take!(pwlen)
+       >> share: take!(bcc - (6 + pwlen))
+       >> service: take!(6)
+       >> (SmbRecordTreeConnectAndX {
+                len:bcc as usize - (6 + pwlen as usize) as usize,
+                share:share,
+           }))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct SmbRecordTransRequest<'a> {
+    pub params: SmbRecordTransRequestParams,
+    pub pipe: Option<SmbPipeProtocolRecord<'a>>,
+    pub txname: SmbRecordTransRequestTxname<'a>,
+    pub data: SmbRecordTransRequestData<'a>,
+}
+
+#[derive(Debug,PartialEq)]
+pub struct SmbPipeProtocolRecord<'a> {
+    pub function: u16,
+    pub fid: &'a[u8],
+}
+
+named!(pub parse_smb_trans_request_record_pipe<SmbPipeProtocolRecord>,
+    dbg_dmp!(do_parse!(
+            fun: le_u16
+        >>  fid: take!(2)
+        >> (SmbPipeProtocolRecord {
+                function: fun,
+                fid: fid,
+            })
+    ))
+);
+
+
+#[derive(Debug,PartialEq)]
+pub struct SmbRecordTransRequestParams<> {
+    pub max_data_cnt: u16,
+    param_cnt: u16,
+    param_offset: u16,
+    data_cnt: u16,
+    data_offset: u16,
+    bcc: u16,
+}
+
+named!(pub parse_smb_trans_request_record_params<(SmbRecordTransRequestParams, Option<SmbPipeProtocolRecord>)>,
+    dbg_dmp!(do_parse!(
+          wct: le_u8
+       >> total_param_cnt: le_u16
+       >> total_data_count: le_u16
+       >> max_param_cnt: le_u16
+       >> max_data_cnt: le_u16
+       >> max_setup_cnt: le_u8
+       >> take!(1) // reserved
+       >> take!(2) // flags
+       >> timeout: le_u32
+       >> take!(2) // reserved
+       >> param_cnt: le_u16
+       >> param_offset: le_u16
+       >> data_cnt: le_u16
+       >> data_offset: le_u16
+       >> setup_cnt: le_u8
+       >> take!(1) // reserved
+       >> pipe: cond!(wct == 16 && setup_cnt == 2, parse_smb_trans_request_record_pipe) // reserved
+       >> bcc: le_u16
+       >> (( SmbRecordTransRequestParams {
+                max_data_cnt:max_data_cnt,
+                param_cnt:param_cnt,
+                param_offset:param_offset,
+                data_cnt:data_cnt,
+                data_offset:data_offset,
+                bcc:bcc,
+            },
+            pipe))))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct SmbRecordTransRequestTxname<'a> {
+    pub name: &'a[u8],
+}
+
+pub fn parse_smb_trans_request_tx_name_ascii(i: &[u8])
+    -> IResult<&[u8], SmbRecordTransRequestTxname>
+{
+    do_parse!(i,
+            name: take_until_and_consume!("\0")
+        >> (SmbRecordTransRequestTxname {
+                name: name,
+            })
+    )
+}
+
+pub fn parse_smb_trans_request_tx_name_unicode(i: &[u8], offset: usize)
+    -> IResult<&[u8], SmbRecordTransRequestTxname>
+{
+    do_parse!(i,
+            cond!(offset % 2 == 1, take!(1))
+        >>  name: take_until_and_consume!("\0\0\0")
+        >> (SmbRecordTransRequestTxname {
+                name: name,
+            })
+    )
+}
+
+#[derive(Debug,PartialEq)]
+pub struct SmbRecordTransRequestData<'a> {
+    pub data: &'a[u8],
+}
+
+pub fn parse_smb_trans_request_record_data(i: &[u8],
+        pad1: usize, param_cnt: u16, pad2: usize, data_len: u16)
+    -> IResult<&[u8], SmbRecordTransRequestData>
+{
+    do_parse!(i,
+            take!(pad1)
+        >>  take!(param_cnt)
+        >>  take!(pad2)
+        >>  data: take!(data_len)
+        >> (SmbRecordTransRequestData {
+                data:data,
+            })
+    )
+}
+
+pub fn parse_smb_trans_request_record<'a, 'b>(i: &'a[u8], r: &SmbRecord<'b>)
+    -> IResult<&'a[u8], SmbRecordTransRequest<'a>>
+{
+    let (rem, (params, pipe)) = match parse_smb_trans_request_record_params(i) {
+        IResult::Done(rem, (rd, p)) => (rem, (rd, p)),
+        IResult::Incomplete(ii) => {
+            return IResult::Incomplete(ii);
+        }
+        IResult::Error(e) => {
+            return IResult::Error(e);
+        }
+    };
+    let mut offset = 32 + (i.len() - rem.len()); // init with SMB header
+    SCLogDebug!("params {:?}: offset {}", params, offset);
+
+    let name = if r.flags2 & 0x8000_u16 != 0 { // unicode
+        SCLogDebug!("unicode flag set");
+        parse_smb_trans_request_tx_name_unicode(rem, offset)
+    } else {
+        SCLogDebug!("unicode flag NOT set");
+        parse_smb_trans_request_tx_name_ascii(rem)
+    };
+    let (rem2, n) = match name {
+        IResult::Done(rem, rd) => (rem, rd),
+        IResult::Incomplete(ii) => {
+            return IResult::Incomplete(ii);
+        }
+        IResult::Error(e) => {
+            return IResult::Error(e);
+        }
+    };
+    offset += rem.len() - rem2.len();
+    SCLogDebug!("n {:?}: offset {}", n, offset);
+
+    // spec says pad to 4 bytes, but traffic shows this doesn't
+    // always happen.
+    let pad1 = if offset == params.param_offset as usize ||
+                  offset == params.data_offset as usize {
+        0
+    } else {
+        offset % 4
+    };
+    SCLogDebug!("pad1 {}", pad1);
+    offset += pad1;
+    offset += params.param_cnt as usize;
+
+    let recdata = if params.data_cnt > 0 {
+        // ignore padding rule if we're already at the correct
+        // offset.
+        let pad2 = if offset == params.data_offset as usize {
+            0
+        } else {
+            offset % 4
+        };
+        SCLogDebug!("pad2 {}", pad2);
+
+        let d = match parse_smb_trans_request_record_data(rem2,
+                pad1, params.param_cnt, pad2, params.data_cnt) {
+            IResult::Done(_, rd) => rd,
+                IResult::Incomplete(ii) => {
+                    return IResult::Incomplete(ii);
+                }
+            IResult::Error(e) => {
+                return IResult::Error(e);
+            }
+        };
+        SCLogDebug!("d {:?}", d);
+        d
+    } else {
+        SmbRecordTransRequestData { data: &[], } // no data
+    };
+
+    let res = SmbRecordTransRequest {
+        params: params, pipe: pipe, txname: n, data: recdata,
+    };
+    IResult::Done(&rem, res)
+}
+
+
+#[derive(Debug,PartialEq)]
+pub struct SmbRecordTransResponse<'a> {
+    pub data_cnt: u16,
+    pub bcc: u16,
+    pub data: &'a[u8],
+}
+
+named!(pub parse_smb_trans_response_error_record<SmbRecordTransResponse>,
+    do_parse!(
+          wct: le_u8
+       >> bcc: le_u16
+       >> (SmbRecordTransResponse {
+                data_cnt:0,
+                bcc:bcc,
+                data:&[],
+           }))
+);
+
+named!(pub parse_smb_trans_response_regular_record<SmbRecordTransResponse>,
+    do_parse!(
+          wct: le_u8
+       >> total_param_cnt: le_u16
+       >> total_data_count: le_u16
+       >> take!(2) // reserved
+       >> param_cnt: le_u16
+       >> param_offset: le_u16
+       >> param_displacement: le_u16
+       >> data_cnt: le_u16
+       >> data_offset: le_u16
+       >> data_displacement: le_u16
+       >> setup_cnt: le_u8
+       >> take!(1) // reserved
+       >> bcc: le_u16
+       >> take!(1) // padding
+       >> data: take!(data_cnt)
+       >> (SmbRecordTransResponse {
+                data_cnt:data_cnt,
+                bcc:bcc,
+                data:data,
+           }))
+);
+
+named!(pub parse_smb_trans_response_record<SmbRecordTransResponse>,
+    switch!(peek!(le_u8), // wct
+        0 => call!(parse_smb_trans_response_error_record) |
+        _ => call!(parse_smb_trans_response_regular_record))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct SmbRecordSetupAndX<'a> {
+    pub sec_blob: &'a[u8],
+}
+
+named!(pub parse_smb_setup_andx_record<SmbRecordSetupAndX>,
+    do_parse!(
+       skip1: take!(15)
+       >> sec_blob_len: le_u16
+       >> skip2: take!(8)
+       >> bcc: le_u16
+       >> sec_blob: take!(sec_blob_len)
+       >> skip3: rest
+       >> (SmbRecordSetupAndX {
+                sec_blob:sec_blob,
+           }))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct SmbRequestReadAndXRecord<'a> {
+    pub fid: &'a[u8],
+    pub size: u64,
+    pub offset: u64,
+}
+
+named!(pub parse_smb_read_andx_request_record<SmbRequestReadAndXRecord>,
+    do_parse!(
+            wtc: le_u8
+        >>  andx_command: le_u8
+        >>  take!(1)    // reserved
+        >>  andx_offset: le_u16
+        >>  fid: take!(2)
+        >>  offset: le_u32
+        >>  max_count_low: le_u16
+        >>  take!(2)
+        >>  max_count_high: le_u32
+        >>  take!(2)
+        >>  high_offset: cond!(wtc==12,le_u32) // only from wtc ==12?
+
+        >> (SmbRequestReadAndXRecord {
+                fid:fid,
+                size: (((max_count_high as u64) << 16)|max_count_low as u64),
+                offset: if high_offset != None { ((high_offset.unwrap() as u64) << 32)|(offset as u64) } else { 0 },
+           }))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct SmbResponseReadAndXRecord<'a> {
+    pub len: u32,
+    pub data: &'a[u8],
+}
+
+named!(pub parse_smb_read_andx_response_record<SmbResponseReadAndXRecord>,
+    do_parse!(
+            wtc: le_u8
+        >>  andx_command: le_u8
+        >>  take!(1)    // reserved
+        >>  andx_offset: le_u16
+        >>  take!(6)
+        >>  data_len_low: le_u16
+        >>  data_offset: le_u16
+        >>  data_len_high: le_u32
+        >>  take!(6)    // reserved
+        >>  bcc: le_u16
+        >>  padding: cond!(bcc > data_len_low, take!(bcc - data_len_low)) // TODO figure out how this works with data_len_high
+        >>  file_data: rest
+
+        >> (SmbResponseReadAndXRecord {
+                len: (((data_len_high as u32) << 16)|data_len_low as u32),
+                data:file_data,
+           }))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct SmbRequestCreateAndXRecord<'a> {
+    pub disposition: u32,
+    pub create_options: u32,
+    pub file_name: &'a[u8],
+}
+
+named!(pub parse_smb_create_andx_request_record<SmbRequestCreateAndXRecord>,
+    do_parse!(
+       skip1: take!(6)
+       >> file_name_len: le_u16
+       >> skip3: take!(28)
+       >> disposition: le_u32
+       >> create_options: le_u32
+       >> skip2: take!(8)
+       >> file_name: take!(file_name_len)
+       >> skip3: rest
+       >> (SmbRequestCreateAndXRecord {
+                disposition: disposition,
+                create_options: create_options,
+                file_name: file_name,
+           }))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct SmbResponseCreateAndXRecord<'a> {
+    pub fid: &'a[u8],
+    pub file_size: u64,
+}
+
+named!(pub parse_smb_create_andx_response_record<SmbResponseCreateAndXRecord>,
+    do_parse!(
+            wct: le_u8
+        >>  andx_command: le_u8
+        >>  take!(1)
+        >>  andx_offset: le_u16
+        >>  oplock_level: le_u8
+        >>  fid: take!(2)
+        >>  create_action: le_u32
+        >>  take!(36)
+        >>  file_size: le_u64
+        >>  take!(8)
+        >>  file_type: le_u16
+        >>  take!(2)
+        >>  is_dir: le_u8
+        >> (SmbResponseCreateAndXRecord {
+                fid:fid,
+                file_size:file_size,
+           }))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct SmbRequestCloseRecord<'a> {
+    pub fid: &'a[u8],
+}
+
+named!(pub parse_smb1_close_request_record<SmbRequestCloseRecord>,
+    do_parse!(
+            take!(1)
+        >>  fid: take!(2)
+       >> (SmbRequestCloseRecord {
+                fid:fid,
+           }))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct SmbVersion<> {
+    pub version: u8,
+//    pub data: &'a[u8],
+}
+
+named!(pub parse_smb_version<SmbVersion>,
+    do_parse!(
+        version: le_u8
+        >> tag!("SMB")
+//        >> data: rest
+        >> (SmbVersion {
+                version:version,
+//                data:data,
+            }))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct SmbRecord<'a> {
+    //pub nbss_hdr: NbssRecord<'a>,
+    pub greeter: &'a[u8],
+
+    pub command: u8,
+    pub is_dos_error: bool,
+    pub nt_status: u32,
+    pub flags: u8,
+    pub flags2: u16,
+
+    pub tree_id: u16,
+    pub user_id: u16,
+    pub multiplex_id: u16,
+
+    pub process_id: u32,
+    pub ssn_id: u32,
+
+    pub data: &'a[u8],
+}
+
+named!(pub parse_smb_record<SmbRecord>,
+    do_parse!(
+            server_component:take!(4) // ff SMB
+        >>  command:le_u8
+        >>  nt_status:le_u32
+        >>  flags:le_u8
+        >>  flags2:le_u16
+        >>  process_id_high:le_u16
+        >>  signature:take!(8)
+        >>  reserved:take!(2)
+        >>  tree_id:le_u16
+        >>  process_id:le_u16
+        >>  user_id:le_u16
+        >>  multiplex_id:le_u16
+        >>  data: rest
+
+        >>  (SmbRecord {
+                greeter:server_component,
+                command:command,
+                nt_status:nt_status,
+                flags:flags,
+                flags2:flags2,
+                is_dos_error: ((flags2 & 0x4000 == 0) && nt_status != 0),
+                tree_id:tree_id,
+                user_id:user_id,
+                multiplex_id:multiplex_id,
+
+                process_id: (process_id_high as u32) << 16 | process_id as u32,
+                //ssn_id: (((process_id as u32)<< 16)|(user_id as u32)),
+                ssn_id: user_id as u32,
+                data:data,
+            })
+));
diff --git a/rust/src/smb/smb2.rs b/rust/src/smb/smb2.rs
new file mode 100644 (file)
index 0000000..0a7acf1
--- /dev/null
@@ -0,0 +1,784 @@
+/* Copyright (C) 2017 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use core::*;
+use log::*;
+use nom::IResult;
+
+use smb::smb::*;
+use smb::smb2_records::*;
+use smb::dcerpc::*;
+use smb::events::*;
+use smb::auth::*;
+use smb::files::*;
+
+pub const SMB2_COMMAND_NEGOTIATE_PROTOCOL:      u16 = 0;
+pub const SMB2_COMMAND_SESSION_SETUP:           u16 = 1;
+pub const SMB2_COMMAND_SESSION_LOGOFF:          u16 = 2;
+pub const SMB2_COMMAND_TREE_CONNECT:            u16 = 3;
+pub const SMB2_COMMAND_TREE_DISCONNECT:         u16 = 4;
+pub const SMB2_COMMAND_CREATE:                  u16 = 5;
+pub const SMB2_COMMAND_CLOSE:                   u16 = 6;
+pub const SMB2_COMMAND_READ:                    u16 = 8;
+pub const SMB2_COMMAND_WRITE:                   u16 = 9;
+pub const SMB2_COMMAND_IOCTL:                   u16 = 11;
+pub const SMB2_COMMAND_KEEPALIVE:               u16 = 13;
+pub const SMB2_COMMAND_FIND:                    u16 = 14;
+pub const SMB2_COMMAND_GET_INFO:                u16 = 16;
+pub const SMB2_COMMAND_SET_INFO:                u16 = 17;
+
+pub fn smb2_command_string(c: u16) -> String {
+    match c {
+        SMB2_COMMAND_NEGOTIATE_PROTOCOL     => "SMB2_COMMAND_NEGOTIATE_PROTOCOL",
+        SMB2_COMMAND_SESSION_SETUP          => "SMB2_COMMAND_SESSION_SETUP",
+        SMB2_COMMAND_SESSION_LOGOFF         => "SMB2_COMMAND_SESSION_LOGOFF",
+        SMB2_COMMAND_TREE_CONNECT           => "SMB2_COMMAND_TREE_CONNECT",
+        SMB2_COMMAND_TREE_DISCONNECT        => "SMB2_COMMAND_TREE_DISCONNECT",
+        SMB2_COMMAND_CREATE                 => "SMB2_COMMAND_CREATE",
+        SMB2_COMMAND_CLOSE                  => "SMB2_COMMAND_CLOSE",
+        SMB2_COMMAND_READ                   => "SMB2_COMMAND_READ",
+        SMB2_COMMAND_WRITE                  => "SMB2_COMMAND_WRITE",
+        SMB2_COMMAND_IOCTL                  => "SMB2_COMMAND_IOCTL",
+        SMB2_COMMAND_KEEPALIVE              => "SMB2_COMMAND_KEEPALIVE",
+        SMB2_COMMAND_FIND                   => "SMB2_COMMAND_FIND",
+        SMB2_COMMAND_GET_INFO               => "SMB2_COMMAND_GET_INFO",
+        SMB2_COMMAND_SET_INFO               => "SMB2_COMMAND_SET_INFO",
+        _ => { return (c).to_string(); },
+    }.to_string()
+
+}
+
+pub fn smb2_dialect_string(d: u16) -> String {
+    match d {
+        0x0202 => "2.02",
+        0x0210 => "2.10",
+        0x0222 => "2.22",
+        0x0224 => "2.24",
+        0x02ff => "2.??",
+        0x0300 => "3.00",
+        0x0302 => "3.02",
+        0x0310 => "3.10",
+        0x0311 => "3.11",
+        _ => { return (d).to_string(); },
+    }.to_string()
+}
+
+// later we'll use this to determine if we need to
+// track a ssn per type
+fn smb2_create_new_tx(_cmd: u16) -> bool {
+    true
+}
+
+fn smb2_read_response_record_generic<'b>(state: &mut SMBState, r: &Smb2Record<'b>)
+{
+    if smb2_create_new_tx(r.command) {
+        let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX);
+        let tx = state.get_generic_tx(2, r.command as u16, &tx_hdr);
+        if let Some(tx) = tx {
+            tx.set_status(r.nt_status, false);
+            tx.response_done = true;
+        }
+    }
+}
+
+pub fn smb2_read_response_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>)
+{
+    smb2_read_response_record_generic(state, r);
+
+    if r.nt_status != SMB_NTSTATUS_SUCCESS {
+        return;
+    }
+
+    match parse_smb2_response_read(r.data) {
+        IResult::Done(_, rd) => {
+            SCLogDebug!("SMBv2: read response => {:?}", rd);
+
+            // get the request info. If we don't have it, there is nothing
+            // we can do except skip this record.
+            let guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_OFFSET);
+            let (offset, file_guid) = match state.ssn2vecoffset_map.remove(&guid_key) {
+                Some(o) => (o.offset, o.guid),
+                None => {
+                    SCLogDebug!("SMBv2 READ response: reply to unknown request");
+                    state.skip_tc = rd.len - rd.data.len() as u32;
+                    return;
+                },
+            };
+            SCLogDebug!("SMBv2 READ: GUID {:?} offset {}", file_guid, offset);
+
+            // look up existing tracker and if we have it update it
+            let found = match state.get_file_tx_by_guid(&file_guid, STREAM_TOCLIENT) {
+                Some((tx, files, flags)) => {
+                    if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+                        let file_id : u32 = tx.id as u32;
+                        filetracker_newchunk(&mut tdf.file_tracker, files, flags,
+                                &tdf.file_name, rd.data, offset,
+                                rd.len, 0, false, &file_id);
+                    }
+                    true
+                },
+                None => { false },
+            };
+            if !found {
+                let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE);
+                let (share_name, is_pipe) = match state.ssn2tree_map.get(&tree_key) {
+                    Some(n) => (n.name.to_vec(), n.is_pipe),
+                    _ => { (Vec::new(), false) },
+                };
+                let is_dcerpc = is_pipe && match state.get_service_for_guid(&file_guid) {
+                    (_, x) => x,
+                };
+                if is_pipe && is_dcerpc {
+                    SCLogDebug!("SMBv2 DCERPC read");
+                    let hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER);
+                    let vercmd = SMBVerCmdStat::new2(SMB2_COMMAND_READ);
+                    smb_read_dcerpc_record(state, vercmd, hdr, &file_guid, rd.data);
+                } else if is_pipe {
+                    SCLogDebug!("non-DCERPC pipe");
+                    state.skip_tc = rd.len - rd.data.len() as u32;
+                } else {
+                    let file_name = match state.guid2name_map.get(&file_guid) {
+                        Some(n) => { n.to_vec() },
+                        None => { Vec::new() },
+                    };
+                    let (tx, files, flags) = state.new_file_tx(&file_guid, &file_name, STREAM_TOCLIENT);
+                    if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+                        let file_id : u32 = tx.id as u32;
+                        filetracker_newchunk(&mut tdf.file_tracker, files, flags,
+                                &file_name, rd.data, offset,
+                                rd.len, 0, false, &file_id);
+                        tdf.share_name = share_name;
+                    }
+                    tx.vercmd.set_smb2_cmd(SMB2_COMMAND_READ);
+                    tx.hdr = SMBCommonHdr::new(SMBHDR_TYPE_HEADER,
+                            r.session_id, r.tree_id, 0); // TODO move into new_file_tx
+                }
+            }
+
+            state.file_tc_left = rd.len - rd.data.len() as u32;
+            state.file_tc_guid = file_guid.to_vec();
+            SCLogDebug!("SMBv2 READ RESPONSE: {} bytes left", state.file_tc_left);
+        }
+        _ => {
+            state.set_event(SMBEvent::MalformedData);
+        }
+    }
+}
+
+pub fn smb2_write_request_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>)
+{
+    if smb2_create_new_tx(r.command) {
+        let tx_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX);
+        let tx = state.new_generic_tx(2, r.command, tx_key);
+        tx.request_done = true;
+    }
+    match parse_smb2_request_write(r.data) {
+        IResult::Done(_, wr) => {
+            /* update key-guid map */
+            let guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_GUID);
+            state.ssn2vec_map.insert(guid_key, wr.guid.to_vec());
+
+            let file_guid = wr.guid.to_vec();
+            let file_name = match state.guid2name_map.get(&file_guid) {
+                Some(n) => n.to_vec(),
+                None => Vec::new(),
+            };
+
+            let found = match state.get_file_tx_by_guid(&file_guid, STREAM_TOSERVER) {
+                Some((tx, files, flags)) => {
+                    if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+                        let file_id : u32 = tx.id as u32;
+                        filetracker_newchunk(&mut tdf.file_tracker, files, flags,
+                                &file_name, wr.data, wr.wr_offset,
+                                wr.wr_len, 0, false, &file_id);
+                    }
+                    true
+                },
+                None => { false },
+            };
+            if !found {
+                let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE);
+                let is_pipe = match state.ssn2tree_map.get(&tree_key) {
+                    Some(n) => { n.is_pipe },
+                    _ => { false },
+                };
+                let is_dcerpc = is_pipe && match state.get_service_for_guid(&wr.guid) {
+                    (_, x) => x,
+                };
+                if is_pipe && is_dcerpc {
+                    SCLogDebug!("SMBv2 DCERPC write");
+                    let hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER);
+                    let vercmd = SMBVerCmdStat::new2(SMB2_COMMAND_WRITE);
+                    smb_write_dcerpc_record(state, vercmd, hdr, wr.data);
+                } else if is_pipe {
+                    SCLogDebug!("non-DCERPC pipe: skip rest of the record");
+                    state.skip_ts = wr.wr_len - wr.data.len() as u32;
+                } else {
+                    let (tx, files, flags) = state.new_file_tx(&file_guid, &file_name, STREAM_TOSERVER);
+                    if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+                        let file_id : u32 = tx.id as u32;
+                        filetracker_newchunk(&mut tdf.file_tracker, files, flags,
+                                &file_name, wr.data, wr.wr_offset,
+                                wr.wr_len, 0, false, &file_id);
+                    }
+                    tx.vercmd.set_smb2_cmd(SMB2_COMMAND_WRITE);
+                    tx.hdr = SMBCommonHdr::new(SMBHDR_TYPE_HEADER,
+                            r.session_id, r.tree_id, 0); // TODO move into new_file_tx
+                }
+            }
+            state.file_ts_left = wr.wr_len - wr.data.len() as u32;
+            state.file_ts_guid = file_guid.to_vec();
+            SCLogDebug!("SMBv2 WRITE REQUEST: {} bytes left", state.file_ts_left);
+
+        },
+        _ => {
+            state.set_event(SMBEvent::MalformedData);
+        },
+    }
+}
+
+pub fn smb2_request_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>)
+{
+    SCLogDebug!("SMBv2 request record, command {} tree {} session {}",
+            &smb2_command_string(r.command), r.tree_id, r.session_id);
+
+    let key_session_id = r.session_id;
+    let mut key_tree_id = r.tree_id;
+    let key_message_id = r.message_id;
+    let mut events : Vec<SMBEvent> = Vec::new();
+
+    let have_tx = match r.command {
+        SMB2_COMMAND_IOCTL => {
+            // some IOCTL responses don't set the tree id
+            key_tree_id = 0;
+
+            let have_ioctl_tx = match parse_smb2_request_ioctl(r.data) {
+                IResult::Done(_, rd) => {
+                    SCLogDebug!("IOCTL request data: {:?}", rd);
+                    let is_dcerpc = rd.is_pipe && match state.get_service_for_guid(&rd.guid) {
+                        (_, x) => x,
+                    };
+                    if is_dcerpc {
+                        SCLogDebug!("IOCTL request data is_pipe. Calling smb_write_dcerpc_record");
+                        let hdr = SMBCommonHdr::new(SMBHDR_TYPE_HEADER,
+                                key_session_id, key_tree_id, key_message_id);
+                        let vercmd = SMBVerCmdStat::new2(SMB2_COMMAND_IOCTL);
+                        smb_write_dcerpc_record(state, vercmd, hdr, rd.data)
+                    } else {
+                        false
+                    }
+                },
+                _ => { false },
+            };
+            have_ioctl_tx
+        },
+        SMB2_COMMAND_TREE_DISCONNECT => {
+            let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE);
+            state.ssn2tree_map.remove(&tree_key);
+            false
+        }
+        SMB2_COMMAND_NEGOTIATE_PROTOCOL => {
+            match parse_smb2_request_negotiate_protocol(r.data) {
+                IResult::Done(_, rd) => {
+                    let mut dialects : Vec<Vec<u8>> = Vec::new();
+                    for d in rd.dialects_vec {
+                        SCLogDebug!("dialect {:x} => {}", d, &smb2_dialect_string(d));
+                        let dvec = smb2_dialect_string(d).as_bytes().to_vec();
+                        dialects.push(dvec);
+                    }
+
+                    let found = match state.get_negotiate_tx(2) {
+                        Some(_) => {
+                            SCLogNotice!("WEIRD, should not have NEGOTIATE tx!");
+                            true
+                        },
+                        None => { false },
+                    };
+                    if !found {
+                        let tx = state.new_negotiate_tx(2);
+                        if let Some(SMBTransactionTypeData::NEGOTIATE(ref mut tdn)) = tx.type_data {
+                            tdn.dialects2 = dialects;
+                        }
+                        tx.request_done = true;
+                    }
+                    true
+                },
+                _ => {
+                    events.push(SMBEvent::MalformedData);
+                    false
+                },
+            }
+        },
+        SMB2_COMMAND_SESSION_SETUP => {
+            SCLogDebug!("SMB2_COMMAND_SESSION_SETUP: r.data.len() {}", r.data.len());
+            match parse_smb2_request_session_setup(r.data) {
+                IResult::Done(_, setup) => {
+                    SCLogDebug!("SMB2_COMMAND_SESSION_SETUP parsed. rd.data.len() {}", setup.data.len());
+                    parse_secblob(state, setup.data);
+/*
+                    match parse_ntlmssp(rd.data) {
+                        IResult::Done(_, nd) => {
+                            SCLogDebug!("NTLMSSP TYPE {}/{} nd {:?}",
+                                    nd.msg_type, &ntlmssp_type_string(nd.msg_type), nd);
+                            match nd.msg_type {
+                                NTLMSSP_NEGOTIATE => {
+                                    key_session_id = 0;
+                                },
+                                NTLMSSP_AUTH => {
+                                    match parse_ntlm_auth_record(nd.data) {
+                                        IResult::Done(_, ad) => {
+                                            SCLogDebug!("auth data {:?}", ad);
+                                            state.ntlmssp_host = ad.host.to_vec();
+                                            state.ntlmssp_user = ad.user.to_vec();
+                                            state.ntlmssp_domain = ad.domain.to_vec();
+                                        },
+                                        _ => {
+                                            events.push(SMBEvent::MalformedData);
+                                        },
+                                    }
+                                },
+                                _ => { },
+                            }
+                        },
+                        _ => {
+                            SCLogNotice!("error parsing ntlmssp");
+                        },
+                    }
+*/
+                },
+                _ => {
+                    events.push(SMBEvent::MalformedData);
+                },
+            }
+            false
+        },
+        SMB2_COMMAND_TREE_CONNECT => {
+            match parse_smb2_request_tree_connect(r.data) {
+                IResult::Done(_, tr) => {
+                    let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_TREE);
+                    let mut name_val = tr.share_name.to_vec();
+                    name_val.retain(|&i|i != 0x00);
+                    if name_val.len() > 1 {
+                        name_val = name_val[1..].to_vec();
+                    }
+
+                    let tx = state.new_treeconnect_tx(name_key, name_val);
+                    tx.request_done = true;
+                    tx.vercmd.set_smb2_cmd(SMB2_COMMAND_TREE_CONNECT);
+                    true
+                }
+                _ => {
+                    events.push(SMBEvent::MalformedData);
+                    false
+                },
+            }
+        },
+        SMB2_COMMAND_READ => {
+            match parse_smb2_request_read(r.data) {
+                IResult::Done(_, rd) => {
+                    SCLogDebug!("SMBv2 READ: GUID {:?} requesting {} bytes at offset {}",
+                            rd.guid, rd.rd_len, rd.rd_offset);
+
+                    // store read guid,offset in map
+                    let guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_OFFSET);
+                    let guidoff = SMBFileGUIDOffset::new(rd.guid.to_vec(), rd.rd_offset);
+                    state.ssn2vecoffset_map.insert(guid_key, guidoff);
+                },
+                _ => {
+                    events.push(SMBEvent::MalformedData);
+                },
+            }
+            false
+        },
+        SMB2_COMMAND_CREATE => {
+            match parse_smb2_request_create(r.data) {
+                IResult::Done(_, cr) => {
+                    let del = cr.create_options & 0x0000_1000 != 0;
+                    let dir = cr.create_options & 0x0000_0001 != 0;
+
+                    SCLogDebug!("create_options {:08x}", cr.create_options);
+
+                    let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_FILENAME);
+                    state.ssn2vec_map.insert(name_key, cr.data.to_vec());
+
+                    let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX);
+                    let tx = state.new_create_tx(&cr.data.to_vec(),
+                            cr.disposition, del, dir, tx_hdr);
+                    tx.vercmd.set_smb2_cmd(r.command);
+                    SCLogDebug!("TS CREATE TX {} created", tx.id);
+                    true
+                },
+                _ => {
+                    events.push(SMBEvent::MalformedData);
+                    false
+                },
+            }
+        },
+        SMB2_COMMAND_WRITE => {
+            smb2_write_request_record(state, &r);
+            true // write handling creates both file tx and generic tx
+        },
+        SMB2_COMMAND_CLOSE => {
+            match parse_smb2_request_close(r.data) {
+                IResult::Done(_, cd) => {
+                    let found_ts = match state.get_file_tx_by_guid(&cd.guid.to_vec(), STREAM_TOSERVER) {
+                        Some((tx, files, flags)) => {
+                            if !tx.request_done {
+                                if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+                                    tdf.file_tracker.close(files, flags);
+                                }
+                            }
+                            tx.request_done = true;
+                            tx.response_done = true;
+                            tx.set_status(SMB_NTSTATUS_SUCCESS, false);
+                            true
+                        },
+                        None => { false },
+                    };
+                    let found_tc = match state.get_file_tx_by_guid(&cd.guid.to_vec(), STREAM_TOCLIENT) {
+                        Some((tx, files, flags)) => {
+                            if !tx.request_done {
+                                if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+                                    tdf.file_tracker.close(files, flags);
+                                }
+                            }
+                            tx.request_done = true;
+                            tx.response_done = true;
+                            tx.set_status(SMB_NTSTATUS_SUCCESS, false);
+                            true
+                        },
+                        None => { false },
+                    };
+                    if !found_ts && !found_tc {
+                        SCLogDebug!("SMBv2: CLOSE(TS): no TX at GUID {:?}", cd.guid);
+                    }
+                },
+                _ => {
+                    events.push(SMBEvent::MalformedData);
+                },
+            }
+            false
+        },
+        _ => {
+            false
+        },
+    };
+    /* if we don't have a tx, create it here (maybe) */
+    if !have_tx {
+        if smb2_create_new_tx(r.command) {
+            let tx_key = SMBCommonHdr::new(SMBHDR_TYPE_GENERICTX,
+                    key_session_id, key_tree_id, key_message_id);
+            let tx = state.new_generic_tx(2, r.command, tx_key);
+            SCLogDebug!("TS TX {} command {} created with session_id {} tree_id {} message_id {}",
+                    tx.id, r.command, key_session_id, key_tree_id, key_message_id);
+            tx.set_events(events);
+        }
+    }
+}
+
+fn smb2_response_record_session_setup<'b>(state: &mut SMBState, r: &Smb2Record<'b>)
+{
+    // first try exact match
+    let found = match state.get_generic_tx(2, r.command, &SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX)) {
+        Some(tx) => {
+            if r.nt_status != SMB_NTSTATUS_PENDING {
+                tx.response_done = true;
+            }
+            tx.set_status(r.nt_status, false);
+            true
+        },
+        None => false,
+    };
+    // if not found try with ssn id 0.
+    if !found {
+        let tx_key = SMBCommonHdr::new(SMBHDR_TYPE_GENERICTX, 0, 0, r.message_id);
+        match state.get_generic_tx(2, r.command, &tx_key) {
+            Some(tx) => {
+                if r.nt_status != SMB_NTSTATUS_PENDING {
+                    tx.response_done = true;
+                }
+                tx.set_status(r.nt_status, false);
+            },
+            None => { },
+        }
+    }
+}
+
+pub fn smb2_response_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>)
+{
+    SCLogDebug!("SMBv2 response record, command {} status {} tree {} session {} message {}",
+            &smb2_command_string(r.command), &smb_ntstatus_string(r.nt_status),
+            r.tree_id, r.session_id, r.message_id);
+
+    let key_session_id = r.session_id;
+    let mut key_tree_id = r.tree_id;
+    let key_message_id = r.message_id;
+    let mut events : Vec<SMBEvent> = Vec::new();
+
+    let have_tx = match r.command {
+        SMB2_COMMAND_IOCTL => {
+            // some IOCTL responses don't set the tree id
+            key_tree_id = 0;
+
+            let have_ioctl_tx = match parse_smb2_response_ioctl(r.data) {
+                IResult::Done(_, rd) => {
+                    SCLogDebug!("IOCTL response data: {:?}", rd);
+
+                    let is_dcerpc = rd.is_pipe && match state.get_service_for_guid(&rd.guid) {
+                        (_, x) => x,
+                    };
+                    if is_dcerpc {
+                        SCLogDebug!("IOCTL response data is_pipe. Calling smb_read_dcerpc_record");
+                        let hdr = SMBCommonHdr::new(SMBHDR_TYPE_HEADER,
+                                key_session_id, key_tree_id, key_message_id);
+                        let vercmd = SMBVerCmdStat::new2_with_ntstatus(SMB2_COMMAND_IOCTL, r.nt_status);
+                        SCLogNotice!("TODO passing empty GUID");
+                        smb_read_dcerpc_record(state, vercmd, hdr, &[],rd.data)
+                    } else {
+                        false
+                    }
+                },
+                _ => {
+                    if r.nt_status == SMB_NTSTATUS_PENDING {
+                        // find tx by ssn/msgid/treeid (+cmd)
+
+                        let tx_key = SMBCommonHdr::new(SMBHDR_TYPE_HEADER,
+                                key_session_id, key_tree_id, key_message_id);
+                        SCLogDebug!("SMB2_COMMAND_IOCTL/SMB_NTSTATUS_PENDING looking for {:?}", tx_key);
+                        match state.get_generic_tx(2, SMB2_COMMAND_IOCTL, &tx_key) {
+                            Some(tx) => {
+                                tx.set_status(r.nt_status, false);
+                                SCLogDebug!("updated status of tx {}", tx.id);
+                                true
+                            },
+                            _ => { false },
+                        }
+                    } else {
+                        SCLogDebug!("parse fail {:?}", r);
+                        events.push(SMBEvent::MalformedData);
+                        false
+                    }
+                },
+            };
+
+            have_ioctl_tx
+        },
+        SMB2_COMMAND_SESSION_SETUP => {
+            smb2_response_record_session_setup(state, r);
+            true
+        },
+        SMB2_COMMAND_WRITE => {
+            match parse_smb2_response_write(r.data) {
+                IResult::Done(_, wr) => {
+                    SCLogDebug!("SMBv2: Write response => {:?}", wr);
+
+                    /* search key-guid map */
+                    let guid_key = SMBCommonHdr::new(SMBHDR_TYPE_GUID,
+                            r.session_id, r.tree_id, r.message_id);
+                    let guid_vec = match state.ssn2vec_map.remove(&guid_key) {
+                        Some(p) => p,
+                        None => {
+                            SCLogDebug!("SMBv2 response: GUID NOT FOUND");
+                            Vec::new()
+                        },
+                    };
+                    SCLogDebug!("SMBv2 write response for GUID {:?}", guid_vec);
+                }
+                _ => {
+                    events.push(SMBEvent::MalformedData);
+                },
+            }
+            false // the request may have created a generic tx, so handle that here
+        },
+        SMB2_COMMAND_READ => {
+            if r.nt_status == SMB_NTSTATUS_SUCCESS {
+                smb2_read_response_record(state, &r);
+                false
+
+            } else if r.nt_status == SMB_NTSTATUS_END_OF_FILE {
+                SCLogDebug!("SMBv2: read response => EOF");
+
+                let guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_OFFSET);
+                let file_guid = match state.ssn2vecoffset_map.remove(&guid_key) {
+                    Some(o) => o.guid,
+                    _ => {
+                        SCLogNotice!("SMBv2 READ response: reply to unknown request");
+                        Vec::new()
+                    },
+                };
+                let found = match state.get_file_tx_by_guid(&file_guid, STREAM_TOCLIENT) {
+                    Some((tx, files, flags)) => {
+                        if !tx.request_done {
+                            if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+                                tdf.file_tracker.close(files, flags);
+                            }
+                        }
+                        tx.set_status(r.nt_status, false);
+                        tx.request_done = true;
+                        false
+                    },
+                    None => { false },
+                };
+                if !found {
+                    SCLogDebug!("SMBv2 READ: no TX at GUID {:?}", file_guid);
+                }
+                false
+            } else {
+                SCLogNotice!("SMBv2 READ: status {}", &smb_ntstatus_string(r.nt_status));
+                false
+            }
+        },
+        SMB2_COMMAND_CREATE => {
+            if r.nt_status == SMB_NTSTATUS_SUCCESS {
+                match parse_smb2_response_create(r.data) {
+                    IResult::Done(_, cr) => {
+                        SCLogDebug!("SMBv2: Create response => {:?}", cr);
+
+                        let guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_FILENAME);
+                        match state.ssn2vec_map.remove(&guid_key) {
+                            Some(mut p) => {
+                                p.retain(|&i|i != 0x00);
+                                state.guid2name_map.insert(cr.guid.to_vec(), p);
+                            },
+                            _ => {
+                                SCLogDebug!("SMBv2 response: GUID NOT FOUND");
+                            },
+                        }
+                    }
+                    _ => {
+                        events.push(SMBEvent::MalformedData);
+                    },
+                }
+            }
+            false
+        },
+        SMB2_COMMAND_TREE_DISCONNECT => {
+            // normally removed when processing request,
+            // but in case we missed that try again here
+            let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE);
+            state.ssn2tree_map.remove(&tree_key);
+            false
+        }
+        SMB2_COMMAND_TREE_CONNECT => {
+            if r.nt_status == SMB_NTSTATUS_SUCCESS {
+                match parse_smb2_response_tree_connect(r.data) {
+                    IResult::Done(_, tr) => {
+                        let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_TREE);
+                        let mut share_name = Vec::new();
+                        let is_pipe = tr.share_type == 2;
+                        let found = match state.get_treeconnect_tx(name_key) {
+                            Some(tx) => {
+                                if let Some(SMBTransactionTypeData::TREECONNECT(ref mut tdn)) = tx.type_data {
+                                    tdn.is_pipe = is_pipe;
+                                    tdn.tree_id = r.tree_id as u32;
+                                    share_name = tdn.share_name.to_vec();
+                                }
+                                // update hdr now that we have a tree_id
+                                tx.hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER);
+                                tx.response_done = true;
+                                tx.set_status(r.nt_status, false);
+                                true
+                            },
+                            None => { false },
+                        };
+                        if found {
+                            let tree = SMBTree::new(share_name.to_vec(), is_pipe);
+                            let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE);
+                            state.ssn2tree_map.insert(tree_key, tree);
+                        }
+                        true
+                    }
+                    _ => {
+                        events.push(SMBEvent::MalformedData);
+                        false
+                    },
+                }
+            } else {
+                let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_TREE);
+                let found = match state.get_treeconnect_tx(name_key) {
+                    Some(tx) => {
+                        tx.response_done = true;
+                        tx.set_status(r.nt_status, false);
+                        true
+                    },
+                    None => { false },
+                };
+                found
+            }
+        },
+        SMB2_COMMAND_NEGOTIATE_PROTOCOL => {
+            match parse_smb2_response_negotiate_protocol(r.data) {
+                IResult::Done(_, rd) => {
+                    SCLogDebug!("SERVER dialect => {}", &smb2_dialect_string(rd.dialect));
+
+                    state.dialect = rd.dialect;
+                    let found2 = match state.get_negotiate_tx(2) {
+                        Some(tx) => {
+                            tx.set_status(r.nt_status, false);
+                            tx.response_done = true;
+                            true
+                        },
+                        None => { false },
+                    };
+                    // SMB2 response to SMB1 request?
+                    let found1 = !found2 && match state.get_negotiate_tx(1) {
+                        Some(tx) => {
+                            tx.set_status(r.nt_status, false);
+                            tx.response_done = true;
+                            true
+                        },
+                        None => {
+                           false
+                        },
+                    };
+                    found1 || found2
+                },
+                _ => {
+                    events.push(SMBEvent::MalformedData);
+                    false
+                }
+            }
+        },
+        _ => {
+            SCLogDebug!("default case: no TX");
+            false
+        },
+    };
+    if !have_tx {
+        let tx_hdr = SMBCommonHdr::new(SMBHDR_TYPE_GENERICTX,
+                key_session_id, key_tree_id, key_message_id);
+        SCLogDebug!("looking for TX {} with session_id {} tree_id {} message_id {}",
+                &smb2_command_string(r.command),
+                key_session_id, key_tree_id, key_message_id);
+        let _found = match state.get_generic_tx(2, r.command, &tx_hdr) {
+            Some(tx) => {
+                SCLogDebug!("tx {} with {}/{} marked as done",
+                        tx.id, r.command, &smb2_command_string(r.command));
+                if r.nt_status != SMB_NTSTATUS_PENDING {
+                    tx.response_done = true;
+                }
+                tx.set_status(r.nt_status, false);
+                tx.set_events(events);
+                true
+            },
+            _ => {
+                SCLogNotice!("no tx found for {:?}", r);
+                false
+            },
+        };
+    }
+}
diff --git a/rust/src/smb/smb2_records.rs b/rust/src/smb/smb2_records.rs
new file mode 100644 (file)
index 0000000..cf93221
--- /dev/null
@@ -0,0 +1,432 @@
+/* Copyright (C) 2017 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use nom::{rest, le_u8, le_u16, le_u32, le_u64, IResult, AsBytes};
+
+#[derive(Debug,PartialEq)]
+pub struct Smb2SecBlobRecord<'a> {
+    pub data: &'a[u8],
+}
+
+named!(pub parse_smb2_sec_blob<Smb2SecBlobRecord>,
+    do_parse!(
+         data: rest
+         >> ( Smb2SecBlobRecord {
+                data: data,
+            })
+));
+
+#[derive(Debug,PartialEq)]
+pub struct Smb2Record<'a> {
+    pub direction: u8,    // 0 req, 1 res
+    pub nt_status: u32,
+    pub command: u16,
+    pub message_id: u64,
+    pub tree_id: u32,
+    pub session_id: u64,
+    pub data: &'a[u8],
+}
+
+named!(pub parse_smb2_request_record<Smb2Record>,
+    do_parse!(
+            server_component:take!(4) // fe SMB
+        >>  hlen: le_u16
+        >>  credit_charge: le_u16
+        >>  channel_seq: le_u16
+        >>  reserved: take!(2)
+        >>  command: le_u16
+        >>  credits_requested: le_u16
+        >>  flags: bits!(tuple!(
+                take_bits!(u8, 2),      // reserved / unused
+                take_bits!(u8, 1),      // replay op
+                take_bits!(u8, 1),      // dfs op
+                take_bits!(u32, 24),    // reserved / unused
+                take_bits!(u8, 1),      // signing
+                take_bits!(u8, 1),      // chained
+                take_bits!(u8, 1),      // async
+                take_bits!(u8, 1)       // response
+            ))
+        >> chain_offset: le_u32
+        >> message_id: le_u64
+        >> process_id: le_u32
+        >> tree_id: le_u32
+        >> session_id: le_u64
+        >> signature: take!(16)
+        // there is probably a cleaner way to do this
+        >> data_c: cond!(chain_offset > hlen as u32, take!(chain_offset - hlen as u32))
+        >> data_r: cond!(chain_offset <= hlen as u32, rest)
+        >> (Smb2Record {
+                direction: flags.7,
+                nt_status: 0,
+                command:command,
+                message_id: message_id,
+                tree_id: tree_id,
+                session_id: session_id,
+                data: if data_c != None { data_c.unwrap() } else { data_r.unwrap() }
+           })
+));
+
+#[derive(Debug,PartialEq)]
+pub struct Smb2NegotiateProtocolRequestRecord<> {
+    pub dialects_vec: Vec<u16>,
+}
+
+named!(pub parse_smb2_request_negotiate_protocol<Smb2NegotiateProtocolRequestRecord>,
+    do_parse!(
+            struct_size: take!(2)
+        >>  dialects_count: le_u16
+        >>  blob1: take!(32)
+        >>  dia_vec: count!(le_u16, dialects_count as usize)
+        >>  blob2: rest
+        >>  (Smb2NegotiateProtocolRequestRecord {
+                dialects_vec: dia_vec,
+            })
+));
+
+#[derive(Debug,PartialEq)]
+pub struct Smb2NegotiateProtocolResponseRecord<> {
+    pub dialect: u16,
+}
+
+named!(pub parse_smb2_response_negotiate_protocol<Smb2NegotiateProtocolResponseRecord>,
+    do_parse!(
+            struct_size: take!(2)
+        >>  skip1: take!(2)
+        >>  dialect: le_u16
+        >>  (Smb2NegotiateProtocolResponseRecord {
+                dialect: dialect,
+            })
+));
+
+#[derive(Debug,PartialEq)]
+pub struct Smb2SessionSetupRequestRecord<'a> {
+    pub data: &'a[u8],
+}
+
+named!(pub parse_smb2_request_session_setup<Smb2SessionSetupRequestRecord>,
+    do_parse!(
+            struct_size: take!(2)
+        >>  flags: le_u8
+        >>  security_mode: le_u8
+        >>  capabilities: le_u32
+        >>  channel: le_u32
+        >>  sec_offset: le_u16
+        >>  sec_len: le_u16
+        >>  prev_ssn_id: take!(8)
+        >>  data: rest
+        >>  (Smb2SessionSetupRequestRecord {
+                data:data,
+            })
+));
+
+
+#[derive(Debug,PartialEq)]
+pub struct Smb2TreeConnectRequestRecord<'a> {
+    pub share_name: &'a[u8],
+}
+
+named!(pub parse_smb2_request_tree_connect<Smb2TreeConnectRequestRecord>,
+    do_parse!(
+            struct_size: take!(2)
+        >>  offset_length: take!(4)
+        >>  data: rest
+        >>  (Smb2TreeConnectRequestRecord {
+                share_name:data,
+            })
+));
+
+#[derive(Debug,PartialEq)]
+pub struct Smb2TreeConnectResponseRecord<> {
+    pub share_type: u8,
+}
+
+named!(pub parse_smb2_response_tree_connect<Smb2TreeConnectResponseRecord>,
+    do_parse!(
+            struct_size: take!(2)
+        >>  share_type: le_u8
+        >>  share_flags: le_u32
+        >>  share_caps: le_u32
+        >>  access_mask: le_u32
+        >>  (Smb2TreeConnectResponseRecord {
+                share_type:share_type,
+            })
+));
+
+
+#[derive(Debug,PartialEq)]
+pub struct Smb2CreateRequestRecord<'a> {
+    pub disposition: u32,
+    pub create_options: u32,
+    pub data: &'a[u8],
+}
+
+named!(pub parse_smb2_request_create<Smb2CreateRequestRecord>,
+    do_parse!(
+            skip1: take!(36)
+        >>  disposition: le_u32
+        >>  create_options: le_u32
+        >>  file_name_offset: le_u16
+        >>  file_name_length: le_u16
+        >>  skip2: take!(8)
+        >>  data: take!(file_name_length)
+        >>  skip3: rest
+        >>  (Smb2CreateRequestRecord {
+                disposition: disposition,
+                create_options: create_options,
+                data:data,
+            })
+));
+
+#[derive(Debug,PartialEq)]
+pub struct Smb2IOCtlRequestRecord<'a> {
+    pub is_pipe: bool,
+    pub guid: &'a[u8],
+    pub data: &'a[u8],
+}
+
+named!(pub parse_smb2_request_ioctl<Smb2IOCtlRequestRecord>,
+    do_parse!(
+            skip: take!(2)  // structure size
+        >>  take!(2)        // reserved
+        >>  func: le_u32
+        >>  guid: take!(16)
+        >>  indata_offset: le_u32
+        >>  indata_len: le_u32
+        >>  take!(4)
+        >>  outdata_offset: le_u32
+        >>  outdata_len: le_u32
+        >>  take!(12)
+        >>  data: take!(indata_len)
+        >>  (Smb2IOCtlRequestRecord {
+                is_pipe: (func == 0x0011c017),
+                guid:guid,
+                data:data,
+            })
+));
+
+#[derive(Debug,PartialEq)]
+pub struct Smb2IOCtlResponseRecord<'a> {
+    pub is_pipe: bool,
+    pub guid: &'a[u8],
+    pub data: &'a[u8],
+    pub indata_len: u32,
+    pub outdata_len: u32,
+    pub indata_offset: u32,
+    pub outdata_offset: u32,
+}
+
+named!(pub parse_smb2_response_ioctl<Smb2IOCtlResponseRecord>,
+    do_parse!(
+            skip: take!(2)  // structure size
+        >>  take!(2)        // reserved
+        >>  func: le_u32
+        >>  guid: take!(16)
+        >>  indata_offset: le_u32
+        >>  indata_len: le_u32
+        >>  outdata_offset: le_u32
+        >>  outdata_len: le_u32
+        >>  take!(8)
+        >>  take!(indata_len)
+        >>  data: take!(outdata_len)
+        >>  (Smb2IOCtlResponseRecord {
+                is_pipe: (func == 0x0011c017),
+                guid:guid,
+                data:data,
+                indata_len:indata_len,
+                outdata_len:outdata_len,
+                indata_offset:indata_offset,
+                outdata_offset:outdata_offset,
+            })
+));
+
+#[derive(Debug,PartialEq)]
+pub struct Smb2CloseRequestRecord<'a> {
+    pub guid: &'a[u8],
+}
+
+named!(pub parse_smb2_request_close<Smb2CloseRequestRecord>,
+    do_parse!(
+            skip: take!(8)
+        >>  guid: take!(16)
+        >>  (Smb2CloseRequestRecord {
+                guid:guid,
+            })
+));
+
+#[derive(Debug,PartialEq)]
+pub struct Smb2WriteRequestRecord<'a> {
+    pub wr_len: u32,
+    pub wr_offset: u64,
+    pub guid: &'a[u8],
+    pub data: &'a[u8],
+}
+
+named!(pub parse_smb2_request_write<Smb2WriteRequestRecord>,
+    do_parse!(
+            skip1: take!(4)
+        >>  wr_len: le_u32
+        >>  wr_offset: le_u64
+        >>  guid: take!(16)
+        >>  channel: le_u32
+        >>  remaining_bytes: le_u32
+        >>  write_flags: le_u32
+        >>  skip2: take!(4)
+        >>  data: rest
+        >>  (Smb2WriteRequestRecord {
+                wr_len:wr_len,
+                wr_offset:wr_offset,
+                guid:guid,
+                data:data,
+            })
+));
+
+#[derive(Debug,PartialEq)]
+pub struct Smb2ReadRequestRecord<'a> {
+    pub rd_len: u32,
+    pub rd_offset: u64,
+    pub guid: &'a[u8],
+}
+
+named!(pub parse_smb2_request_read<Smb2ReadRequestRecord>,
+    do_parse!(
+            skip1: take!(4)
+        >>  rd_len: le_u32
+        >>  rd_offset: le_u64
+        >>  guid: take!(16)
+        >>  min_count: le_u32
+        >>  channel: le_u32
+        >>  remaining_bytes: le_u32
+        >>  skip2: take!(4)
+        >>  (Smb2ReadRequestRecord {
+                rd_len:rd_len,
+                rd_offset:rd_offset,
+                guid:guid,
+            })
+));
+
+#[derive(Debug,PartialEq)]
+pub struct Smb2ReadResponseRecord<'a> {
+    pub len: u32,
+    pub data: &'a[u8],
+}
+
+named!(pub parse_smb2_response_read<Smb2ReadResponseRecord>,
+    do_parse!(
+            skip1: take!(4)
+        >>  rd_len: le_u32
+        >>  skip2: take!(8)
+        >>  data: rest
+        >>  (Smb2ReadResponseRecord {
+                len : rd_len,
+                data : data,
+            })
+));
+
+#[derive(Debug,PartialEq)]
+pub struct Smb2CreateResponseRecord<'a> {
+    pub guid: &'a[u8],
+}
+
+named!(pub parse_smb2_response_create<Smb2CreateResponseRecord>,
+    do_parse!(
+            skip1: take!(64)
+        >>  guid: take!(16)
+        >>  skip2: take!(8)
+        >>  (Smb2CreateResponseRecord {
+                guid : guid,
+            })
+));
+
+#[derive(Debug,PartialEq)]
+pub struct Smb2WriteResponseRecord<> {
+    pub wr_cnt: u32,
+}
+
+named!(pub parse_smb2_response_write<Smb2WriteResponseRecord>,
+    do_parse!(
+            skip1: take!(4)
+        >>  wr_cnt: le_u32
+        >>  skip2: take!(6)
+        >>  (Smb2WriteResponseRecord {
+                wr_cnt : wr_cnt,
+            })
+));
+
+named!(pub parse_smb2_response_record<Smb2Record>,
+    do_parse!(
+            server_component:take!(4) // fe SMB
+        >>  hlen: le_u16
+        >>  credit_charge: le_u16
+        >>  nt_status: le_u32
+        >>  command: le_u16
+        >>  credit_granted: le_u16
+        >>  flags: bits!(tuple!(
+                take_bits!(u8, 2),      // reserved / unused
+                take_bits!(u8, 1),      // replay op
+                take_bits!(u8, 1),      // dfs op
+                take_bits!(u32, 24),    // reserved / unused
+                take_bits!(u8, 1),      // signing
+                take_bits!(u8, 1),      // chained
+                take_bits!(u8, 1),      // async
+                take_bits!(u8, 1)       // response
+            ))
+        >> chain_offset: le_u32
+        >> message_id: le_u64
+        >> process_id: le_u32
+        >> tree_id: le_u32
+        >> session_id: le_u64
+        >> signature: take!(16)
+        // there is probably a cleaner way to do this
+        >> data_c: cond!(chain_offset > hlen as u32, take!(chain_offset - hlen as u32))
+        >> data_r: cond!(chain_offset <= hlen as u32, rest)
+        >> (Smb2Record {
+                direction: flags.7,
+                nt_status: nt_status,
+                message_id: message_id,
+                tree_id: tree_id,
+                session_id: session_id,
+                command:command,
+                data: if data_c != None { data_c.unwrap() } else { data_r.unwrap() }
+           })
+));
+
+#[derive(Debug,PartialEq)]
+pub struct Smb2RecordPostGap<'a> {
+    pub data: &'a[u8],
+}
+
+named!(pub search_smb2_record<Smb2RecordPostGap>,
+    do_parse!(
+           alt!(take_until!([0xfe, 0x53, 0x4d, 0x42].as_bytes())|    // SMB2
+                take_until!([0xff, 0x53, 0x4d, 0x42].as_bytes()))    // SMB1
+        >> data : rest
+        >> ( Smb2RecordPostGap {
+                data:data,
+           })
+));
+
+pub fn search_smb2_record_f<'a>(input: &'a [u8])
+    -> IResult<&'a [u8], Smb2RecordPostGap>
+{
+    return closure!(&'a [u8], do_parse!(
+           take_until!([0xfe, 0x53, 0x4d, 0x42].as_bytes())
+        >> data : rest
+        >> ( Smb2RecordPostGap {
+                data:data,
+           })
+    ))(input);
+}
index 15f4488a1c7216f7d8c21c9a6df712505c31f10a..0004126714b71b50e0ecc76fa26a91ce8b51c400 100644 (file)
@@ -39,6 +39,7 @@ app-layer-parser.c app-layer-parser.h \
 app-layer-protos.c app-layer-protos.h \
 app-layer-smb2.c app-layer-smb2.h \
 app-layer-smb.c app-layer-smb.h \
+app-layer-smb-tcp-rust.c app-layer-smb-tcp-rust.h \
 app-layer-smtp.c app-layer-smtp.h \
 app-layer-nfs-tcp.c app-layer-nfs-tcp.h \
 app-layer-nfs-udp.c app-layer-nfs-udp.h \
@@ -238,6 +239,7 @@ detect-ssh-proto.c detect-ssh-proto.h \
 detect-ssh-proto-version.c detect-ssh-proto-version.h \
 detect-ssh-software.c detect-ssh-software.h \
 detect-ssh-software-version.c detect-ssh-software-version.h \
+detect-smb-share.c detect-smb-share.h \
 detect-ssl-state.c detect-ssl-state.h \
 detect-ssl-version.c detect-ssl-version.h \
 detect-stream_size.c detect-stream_size.h \
@@ -316,6 +318,7 @@ output-json-stats.c output-json-stats.h \
 output-json-tls.c output-json-tls.h \
 output-json-nfs.c output-json-nfs.h \
 output-json-tftp.c output-json-tftp.h \
+output-json-smb.c output-json-smb.h \
 output-json-template.c output-json-template.h \
 output-json-metadata.c output-json-metadata.h \
 output-lua.c output-lua.h \
diff --git a/src/app-layer-smb-tcp-rust.c b/src/app-layer-smb-tcp-rust.c
new file mode 100644 (file)
index 0000000..aef1b7c
--- /dev/null
@@ -0,0 +1,276 @@
+/* Copyright (C) 2017 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include "suricata-common.h"
+#include "suricata.h"
+
+#include "app-layer-protos.h"
+#include "app-layer-detect-proto.h"
+#include "app-layer-parser.h"
+
+#include "util-unittest.h"
+
+#ifdef HAVE_RUST
+#include "rust.h"
+#include "app-layer-smb-tcp-rust.h"
+#include "rust-smb-smb-gen.h"
+#include "rust-smb-files-gen.h"
+
+#define MIN_REC_SIZE 32
+
+static int RustSMBTCPParseRequest(Flow *f, void *state,
+        AppLayerParserState *pstate, uint8_t *input, uint32_t input_len,
+        void *local_data)
+{
+    SCLogDebug("RustSMBTCPParseRequest");
+    uint16_t file_flags = FileFlowToFlags(f, STREAM_TOSERVER);
+    rs_smb_setfileflags(0, state, file_flags|FILE_USE_DETECT);
+
+    int res;
+    if (input == NULL && input_len > 0) {
+        res = rs_smb_parse_request_tcp_gap(state, input_len);
+    } else {
+        res = rs_smb_parse_request_tcp(f, state, pstate, input, input_len,
+            local_data);
+    }
+    if (res != 1) {
+        SCLogNotice("SMB request%s of %u bytes, retval %d",
+                (input == NULL && input_len > 0) ? " is GAP" : "", input_len, res);
+    }
+    return res;
+}
+
+static int RustSMBTCPParseResponse(Flow *f, void *state,
+        AppLayerParserState *pstate, uint8_t *input, uint32_t input_len,
+        void *local_data)
+{
+    SCLogDebug("RustSMBTCPParseResponse");
+    uint16_t file_flags = FileFlowToFlags(f, STREAM_TOCLIENT);
+    rs_smb_setfileflags(1, state, file_flags|FILE_USE_DETECT);
+
+    SCLogDebug("RustSMBTCPParseResponse %p/%u", input, input_len);
+    int res;
+    if (input == NULL && input_len > 0) {
+        res = rs_smb_parse_response_tcp_gap(state, input_len);
+    } else {
+        res = rs_smb_parse_response_tcp(f, state, pstate, input, input_len,
+            local_data);
+    }
+    if (res != 1) {
+        SCLogNotice("SMB response%s of %u bytes, retval %d",
+                (input == NULL && input_len > 0) ? " is GAP" : "", input_len, res);
+    }
+    return res;
+}
+
+static uint16_t RustSMBTCPProbeTS(Flow *f,
+        uint8_t *input, uint32_t len, uint32_t *offset)
+{
+    SCLogDebug("RustSMBTCPProbe");
+/*
+    if (len == 0 || len < sizeof(SMBHeader)) {
+        return ALPROTO_UNKNOWN;
+    }
+*/
+    // Validate and return ALPROTO_FAILED if needed.
+    if (!rs_smb_probe_tcp_ts(input, len)) {
+        return ALPROTO_FAILED;
+    }
+
+    return ALPROTO_SMB;
+}
+
+static uint16_t RustSMBTCPProbeTC(Flow *f,
+        uint8_t *input, uint32_t len, uint32_t *offset)
+{
+    SCLogDebug("RustSMBTCPProbe");
+/*
+    if (len == 0 || len < sizeof(SMBHeader)) {
+        return ALPROTO_UNKNOWN;
+    }
+*/
+    // Validate and return ALPROTO_FAILED if needed.
+    if (!rs_smb_probe_tcp_tc(input, len)) {
+        return ALPROTO_FAILED;
+    }
+
+    return ALPROTO_SMB;
+}
+
+static int RustSMBGetAlstateProgress(void *tx, uint8_t direction)
+{
+    return rs_smb_tx_get_alstate_progress(tx, direction);
+}
+
+static uint64_t RustSMBGetTxCnt(void *alstate)
+{
+    return rs_smb_state_get_tx_count(alstate);
+}
+
+static void *RustSMBGetTx(void *alstate, uint64_t tx_id)
+{
+    return rs_smb_state_get_tx(alstate, tx_id);
+}
+
+static AppLayerGetTxIterTuple RustSMBGetTxIterator(
+        const uint8_t ipproto, const AppProto alproto,
+        void *alstate, uint64_t min_tx_id, uint64_t max_tx_id,
+        AppLayerGetTxIterState *istate)
+{
+    return rs_smb_state_get_tx_iterator(alstate, min_tx_id, (uint64_t *)istate);
+}
+
+
+static void RustSMBSetTxLogged(void *alstate, void *tx, uint32_t logger)
+{
+    rs_smb_tx_set_logged(alstate, tx, logger);
+}
+
+static LoggerId RustSMBGetTxLogged(void *alstate, void *tx)
+{
+    return rs_smb_tx_get_logged(alstate, tx);
+}
+
+static void RustSMBStateTransactionFree(void *state, uint64_t tx_id)
+{
+    rs_smb_state_tx_free(state, tx_id);
+}
+
+static DetectEngineState *RustSMBGetTxDetectState(void *tx)
+{
+    return rs_smb_state_get_tx_detect_state(tx);
+}
+
+static int RustSMBSetTxDetectState(void *tx, DetectEngineState *s)
+{
+    rs_smb_state_set_tx_detect_state(tx, s);
+    return 0;
+}
+
+static FileContainer *RustSMBGetFiles(void *state, uint8_t direction)
+{
+    return rs_smb_getfiles(direction, state);
+}
+
+static AppLayerDecoderEvents *RustSMBGetEvents(void *state, uint64_t id)
+{
+    return rs_smb_state_get_events(state, id);
+}
+
+static int RustSMBGetEventInfo(const char *event_name, int *event_id,
+    AppLayerEventType *event_type)
+{
+    return rs_smb_state_get_event_info(event_name, event_id, event_type);
+}
+
+static void RustSMBSetDetectFlags(void *tx, uint8_t dir, uint64_t flags)
+{
+    rs_smb_tx_set_detect_flags(tx, dir, flags);
+}
+
+static uint64_t RustSMBGetDetectFlags(void *tx, uint8_t dir)
+{
+    return rs_smb_tx_get_detect_flags(tx, dir);
+}
+
+static void RustSMBStateTruncate(void *state, uint8_t direction)
+{
+    return rs_smb_state_truncate(state, direction);
+}
+
+static StreamingBufferConfig sbcfg = STREAMING_BUFFER_CONFIG_INITIALIZER;
+static SuricataFileContext sfc = { &sbcfg };
+
+void RegisterRustSMBTCPParsers(void)
+{
+    const char *proto_name = "smb";
+
+    /** SMB */
+    if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) {
+        AppLayerProtoDetectRegisterProtocol(ALPROTO_SMB, proto_name);
+
+        rs_smb_init(&sfc);
+
+        if (RunmodeIsUnittests()) {
+            AppLayerProtoDetectPPRegister(IPPROTO_TCP, "445", ALPROTO_SMB, 0,
+                    MIN_REC_SIZE, STREAM_TOSERVER, RustSMBTCPProbeTS,
+                    NULL);
+        } else {
+            int have_cfg = AppLayerProtoDetectPPParseConfPorts("tcp",
+                    IPPROTO_TCP, proto_name, ALPROTO_SMB, 0,
+                    MIN_REC_SIZE, RustSMBTCPProbeTS, RustSMBTCPProbeTC);
+            /* if we have no config, we enable the default port 445 */
+            if (!have_cfg) {
+                SCLogWarning(SC_ERR_SMB_CONFIG, "no SMB TCP config found, "
+                                                "enabling SMB detection on "
+                                                "port 445.");
+                AppLayerProtoDetectPPRegister(IPPROTO_TCP, "445", ALPROTO_SMB, 0,
+                        MIN_REC_SIZE, STREAM_TOSERVER, RustSMBTCPProbeTS,
+                        RustSMBTCPProbeTC);
+            }
+        }
+    } else {
+        SCLogInfo("Protocol detection and parser disabled for %s protocol.",
+                  proto_name);
+        return;
+    }
+
+    if (AppLayerParserConfParserEnabled("tcp", proto_name)) {
+        AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_SMB, STREAM_TOSERVER,
+                RustSMBTCPParseRequest);
+        AppLayerParserRegisterParser(IPPROTO_TCP , ALPROTO_SMB, STREAM_TOCLIENT,
+                RustSMBTCPParseResponse);
+        AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_SMB,
+                rs_smb_state_new, rs_smb_state_free);
+        AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_SMB,
+                RustSMBStateTransactionFree);
+
+        AppLayerParserRegisterGetEventsFunc(IPPROTO_TCP, ALPROTO_SMB,
+                RustSMBGetEvents);
+        AppLayerParserRegisterGetEventInfo(IPPROTO_TCP, ALPROTO_SMB,
+                RustSMBGetEventInfo);
+
+        AppLayerParserRegisterDetectStateFuncs(IPPROTO_TCP, ALPROTO_SMB,
+                RustSMBGetTxDetectState, RustSMBSetTxDetectState);
+        AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_SMB, RustSMBGetTx);
+        AppLayerParserRegisterGetTxIterator(IPPROTO_TCP, ALPROTO_SMB, RustSMBGetTxIterator);
+        AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_SMB,
+                RustSMBGetTxCnt);
+        AppLayerParserRegisterLoggerFuncs(IPPROTO_TCP, ALPROTO_SMB,
+                RustSMBGetTxLogged, RustSMBSetTxLogged);
+        AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_SMB,
+                RustSMBGetAlstateProgress);
+        AppLayerParserRegisterGetStateProgressCompletionStatus(ALPROTO_SMB,
+                rs_smb_state_progress_completion_status);
+        AppLayerParserRegisterDetectFlagsFuncs(IPPROTO_TCP, ALPROTO_SMB,
+                                               RustSMBGetDetectFlags, RustSMBSetDetectFlags);
+        AppLayerParserRegisterTruncateFunc(IPPROTO_TCP, ALPROTO_SMB,
+                                          RustSMBStateTruncate);
+        AppLayerParserRegisterGetFilesFunc(IPPROTO_TCP, ALPROTO_SMB, RustSMBGetFiles);
+
+        /* This parser accepts gaps. */
+        AppLayerParserRegisterOptionFlags(IPPROTO_TCP, ALPROTO_SMB,
+                APP_LAYER_PARSER_OPT_ACCEPT_GAPS);
+
+    } else {
+        SCLogInfo("Parsed disabled for %s protocol. Protocol detection"
+                  "still on.", proto_name);
+    }
+
+    return;
+}
+#endif /* HAVE_RUST */
diff --git a/src/app-layer-smb-tcp-rust.h b/src/app-layer-smb-tcp-rust.h
new file mode 100644 (file)
index 0000000..1a85009
--- /dev/null
@@ -0,0 +1,24 @@
+/* Copyright (C) 2017 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef __APP_LAYER_SMB_TCP_RUST_H__
+#define __APP_LAYER_SMB_TCP_RUST_H__
+
+void RegisterRustSMBTCPParsers(void);
+void RegisterSMBParsers(void);
+
+#endif /* !__APP_LAYER_SMB_TCP_RUST_H__ */
index 3d1187113640b02304cc3519888afb68681d3d2e..f9ab87945b93928181929c0dda8e3937ebc323e5 100644 (file)
 #include "util-unittest.h"
 #include "util-memcmp.h"
 
+#ifdef HAVE_RUST
+#include "rust.h"
+#include "app-layer-smb-tcp-rust.h"
+void RegisterSMBParsers(void)
+{
+    RegisterRustSMBTCPParsers();
+}
+
+#else // no RUST
+
 #include "app-layer-smb.h"
 
 enum {
@@ -2803,4 +2813,5 @@ void SMBParserRegisterTests(void)
     UtRegisterTest("SMBParserTest10", SMBParserTest10);
 #endif
 }
+#endif /* HAVE_RUST */
 
index 8e7ec4fadcb07eaa701924ef7e790beadb724c3e..929d3c9214b13b3487893ad59a68ebf43659ac9c 100644 (file)
@@ -32,6 +32,8 @@
 #include "app-layer-dcerpc-common.h"
 #include "app-layer-dcerpc.h"
 
+#ifndef HAVE_RUST
+
 typedef struct SMBHdr_ {
     uint8_t protocol[4];
     uint8_t command;
@@ -159,9 +161,11 @@ typedef struct SMBState_ {
 #define SMB_COM_CLOSE_PRINT_FILE       0xC2
 #define SMB_COM_GET_PRINT_QUEUE                0xC3
 
+int isAndX(SMBState *smb_state);
+#endif /* HAVE_RUST */
+
 void RegisterSMBParsers(void);
 void SMBParserRegisterTests(void);
-int isAndX(SMBState *smb_state);
 
 #endif /* __APP_LAYER_SMB_H__ */
 
index 472399e0f6e2d7fe7dba35ed2742b880bb1a8484..30f21935e5dca2239f7200e5695d0c832ecf3492 100644 (file)
 #include "util-unittest-helper.h"
 #include "stream-tcp.h"
 
+#ifdef HAVE_RUST
+#include "rust.h"
+#include "rust-smb-detect-gen.h"
+#endif
+
 #define PARSE_REGEX "^\\s*([0-9a-zA-Z]{8}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{12})(?:\\s*,(<|>|=|!)([0-9]{1,5}))?(?:\\s*,(any_frag))?\\s*$"
 
 static pcre *parse_regex = NULL;
@@ -55,6 +60,12 @@ static pcre_extra *parse_regex_study = NULL;
 static int DetectDceIfaceMatch(ThreadVars *, DetectEngineThreadCtx *,
         Flow *, uint8_t, void *, void *,
         const Signature *, const SigMatchCtx *);
+#ifdef HAVE_RUST
+static int DetectDceIfaceMatchRust(ThreadVars *t,
+        DetectEngineThreadCtx *det_ctx,
+        Flow *f, uint8_t flags, void *state, void *txv,
+        const Signature *s, const SigMatchCtx *m);
+#endif
 static int DetectDceIfaceSetup(DetectEngineCtx *, Signature *, const char *);
 static void DetectDceIfaceFree(void *);
 static void DetectDceIfaceRegisterTests(void);
@@ -73,7 +84,11 @@ void DetectDceIfaceRegister(void)
 {
     sigmatch_table[DETECT_DCE_IFACE].name = "dce_iface";
     sigmatch_table[DETECT_DCE_IFACE].Match = NULL;
+#ifdef HAVE_RUST
+    sigmatch_table[DETECT_DCE_IFACE].AppLayerTxMatch = DetectDceIfaceMatchRust;
+#else
     sigmatch_table[DETECT_DCE_IFACE].AppLayerTxMatch = DetectDceIfaceMatch;
+#endif
     sigmatch_table[DETECT_DCE_IFACE].Setup = DetectDceIfaceSetup;
     sigmatch_table[DETECT_DCE_IFACE].Free  = DetectDceIfaceFree;
     sigmatch_table[DETECT_DCE_IFACE].RegisterTests = DetectDceIfaceRegisterTests;
@@ -231,6 +246,28 @@ static DetectDceIfaceData *DetectDceIfaceArgParse(const char *arg)
     return NULL;
 }
 
+#include "app-layer-smb.h"
+DCERPCState *DetectDceGetState(AppProto alproto, void *alstate)
+{
+#ifdef HAVE_RUST
+    return alstate;
+#else
+    switch (alproto) {
+        case ALPROTO_DCERPC:
+            return alstate;
+        case ALPROTO_SMB: {
+            SMBState *smb_state = (SMBState *)alstate;
+            return &smb_state->ds;
+        }
+        case ALPROTO_SMB2:
+            // not implemented
+            return NULL;
+    }
+
+    return NULL;
+#endif
+}
+
 /**
  * \internal
  * \brief Internal function that compares the dce interface version for this
@@ -258,24 +295,6 @@ static inline int DetectDceIfaceMatchIfaceVersion(uint16_t version,
     }
 }
 
-#include "app-layer-smb.h"
-DCERPCState *DetectDceGetState(AppProto alproto, void *alstate)
-{
-    switch(alproto) {
-        case ALPROTO_DCERPC:
-            return alstate;
-        case ALPROTO_SMB: {
-            SMBState *smb_state = (SMBState *)alstate;
-            return &smb_state->ds;
-        }
-        case ALPROTO_SMB2:
-            // not implemented
-            return NULL;
-    }
-
-    return NULL;
-}
-
 /**
  * \brief App layer match function for the "dce_iface" keyword.
  *
@@ -297,9 +316,10 @@ static int DetectDceIfaceMatch(ThreadVars *t, DetectEngineThreadCtx *det_ctx,
     SCEnter();
 
     int ret = 0;
+    DetectDceIfaceData *dce_data = (DetectDceIfaceData *)m;
+
     DCERPCUuidEntry *item = NULL;
     int i = 0;
-    DetectDceIfaceData *dce_data = (DetectDceIfaceData *)m;
     DCERPCState *dcerpc_state = DetectDceGetState(f->alproto, f->alstate);
     if (dcerpc_state == NULL) {
         SCLogDebug("No DCERPCState for the flow");
@@ -353,6 +373,33 @@ end:
     SCReturnInt(ret);
 }
 
+#ifdef HAVE_RUST
+static int DetectDceIfaceMatchRust(ThreadVars *t,
+        DetectEngineThreadCtx *det_ctx,
+        Flow *f, uint8_t flags, void *state, void *txv,
+        const Signature *s, const SigMatchCtx *m)
+{
+    SCEnter();
+
+    if (f->alproto == ALPROTO_DCERPC) {
+        return DetectDceIfaceMatch(t, det_ctx, f, flags,
+                                   state, txv, s, m);
+    }
+
+    int ret = 0;
+    DetectDceIfaceData *dce_data = (DetectDceIfaceData *)m;
+
+    if (rs_smb_tx_get_dce_iface(f->alstate, txv, dce_data->uuid, 16, dce_data->op, dce_data->version) != 1) {
+        SCLogDebug("rs_smb_tx_get_dce_iface: didn't match");
+    } else {
+        SCLogDebug("rs_smb_tx_get_dce_iface: matched!");
+        ret = 1;
+        // TODO validate frag
+    }
+    SCReturnInt(ret);
+}
+#endif
+
 /**
  * \brief Creates a SigMatch for the "dce_iface" keyword being sent as argument,
  *        and appends it to the Signature(s).
@@ -370,8 +417,8 @@ static int DetectDceIfaceSetup(DetectEngineCtx *de_ctx, Signature *s, const char
     DetectDceIfaceData *did = NULL;
     SigMatch *sm = NULL;
 
-    if (DetectSignatureSetAppProto(s, ALPROTO_DCERPC) != 0)
-        return -1;
+//    if (DetectSignatureSetAppProto(s, ALPROTO_DCERPC) != 0)
+//        return -1;
 
     did = DetectDceIfaceArgParse(arg);
     if (did == NULL) {
index f76d522f0a0250912a3f0ca7ade05ebb45dced09..28b3c9de084a268f2cfb1011313cbeaa469911a9 100644 (file)
 #include "util-unittest-helper.h"
 #include "stream-tcp.h"
 
+#ifdef HAVE_RUST
+#include "rust.h"
+#include "rust-smb-detect-gen.h"
+#endif
+
 #define PARSE_REGEX "^\\s*([0-9]{1,5}(\\s*-\\s*[0-9]{1,5}\\s*)?)(,\\s*[0-9]{1,5}(\\s*-\\s*[0-9]{1,5})?\\s*)*$"
 
 static pcre *parse_regex = NULL;
@@ -56,6 +61,12 @@ static pcre_extra *parse_regex_study = NULL;
 static int DetectDceOpnumMatch(ThreadVars *, DetectEngineThreadCtx *,
         Flow *, uint8_t, void *, void *,
         const Signature *, const SigMatchCtx *);
+#ifdef HAVE_RUST
+static int DetectDceOpnumMatchRust(ThreadVars *t,
+                        DetectEngineThreadCtx *det_ctx,
+                        Flow *f, uint8_t flags, void *state, void *txv,
+                        const Signature *s, const SigMatchCtx *m);
+#endif
 static int DetectDceOpnumSetup(DetectEngineCtx *, Signature *, const char *);
 static void DetectDceOpnumFree(void *);
 static void DetectDceOpnumRegisterTests(void);
@@ -68,7 +79,11 @@ void DetectDceOpnumRegister(void)
 {
     sigmatch_table[DETECT_DCE_OPNUM].name = "dce_opnum";
     sigmatch_table[DETECT_DCE_OPNUM].Match = NULL;
+#ifdef HAVE_RUST
+    sigmatch_table[DETECT_DCE_OPNUM].AppLayerTxMatch = DetectDceOpnumMatchRust;
+#else
     sigmatch_table[DETECT_DCE_OPNUM].AppLayerTxMatch = DetectDceOpnumMatch;
+#endif
     sigmatch_table[DETECT_DCE_OPNUM].Setup = DetectDceOpnumSetup;
     sigmatch_table[DETECT_DCE_OPNUM].Free  = DetectDceOpnumFree;
     sigmatch_table[DETECT_DCE_OPNUM].RegisterTests = DetectDceOpnumRegisterTests;
@@ -255,15 +270,15 @@ static int DetectDceOpnumMatch(ThreadVars *t, DetectEngineThreadCtx *det_ctx,
         SCLogDebug("No DCERPCState for the flow");
         SCReturnInt(0);
     }
+    uint16_t opnum = dcerpc_state->dcerpc.dcerpcrequest.opnum;
 
     for ( ; dor != NULL; dor = dor->next) {
         if (dor->range2 == DCE_OPNUM_RANGE_UNINITIALIZED) {
-            if (dor->range1 == dcerpc_state->dcerpc.dcerpcrequest.opnum) {
+            if (dor->range1 == opnum) {
                 SCReturnInt(1);
             }
         } else {
-            if (dor->range1 <= dcerpc_state->dcerpc.dcerpcrequest.opnum &&
-                dor->range2 >= dcerpc_state->dcerpc.dcerpcrequest.opnum)
+            if (dor->range1 <= opnum && dor->range2 >= opnum)
             {
                 SCReturnInt(1);
             }
@@ -273,6 +288,44 @@ static int DetectDceOpnumMatch(ThreadVars *t, DetectEngineThreadCtx *det_ctx,
     SCReturnInt(0);
 }
 
+#ifdef HAVE_RUST
+static int DetectDceOpnumMatchRust(ThreadVars *t,
+                        DetectEngineThreadCtx *det_ctx,
+                        Flow *f, uint8_t flags, void *state, void *txv,
+                        const Signature *s, const SigMatchCtx *m)
+{
+    SCEnter();
+
+    if (f->alproto == ALPROTO_DCERPC) {
+        return DetectDceOpnumMatch(t, det_ctx, f, flags,
+                                   state, txv, s, m);
+    }
+
+    const DetectDceOpnumData *dce_data = (DetectDceOpnumData *)m;
+    const DetectDceOpnumRange *dor = dce_data->range;
+
+    uint16_t opnum;
+    if (rs_smb_tx_get_dce_opnum(txv, &opnum) != 1)
+        SCReturnInt(0);
+    SCLogDebug("(rust) opnum %u", opnum);
+
+    for ( ; dor != NULL; dor = dor->next) {
+        if (dor->range2 == DCE_OPNUM_RANGE_UNINITIALIZED) {
+            if (dor->range1 == opnum) {
+                SCReturnInt(1);
+            }
+        } else {
+            if (dor->range1 <= opnum && dor->range2 >= opnum)
+            {
+                SCReturnInt(1);
+            }
+        }
+    }
+
+    SCReturnInt(0);
+}
+#endif
+
 /**
  * \brief Creates a SigMatch for the "dce_opnum" keyword being sent as argument,
  *        and appends it to the Signature(s).
@@ -296,8 +349,8 @@ static int DetectDceOpnumSetup(DetectEngineCtx *de_ctx, Signature *s, const char
         return -1;
     }
 
-    if (DetectSignatureSetAppProto(s, ALPROTO_DCERPC) != 0)
-        return -1;
+    //if (DetectSignatureSetAppProto(s, ALPROTO_DCERPC) != 0)
+    //    return -1;
 
     dod = DetectDceOpnumArgParse(arg);
     if (dod == NULL) {
index 569dad5df485960f736f29fe7058c08e73f1030d..87fa07efb17a99ed2197a64c9d517479a64b2c62 100644 (file)
 
 #include "stream-tcp.h"
 
+#ifdef HAVE_RUST
+#include "rust.h"
+#include "rust-smb-detect-gen.h"
+#endif
+
 #define BUFFER_NAME "dce_stub_data"
 #define KEYWORD_NAME "dce_stub_data"
 
@@ -77,13 +82,26 @@ static void PrefilterTxDceStubDataRequest(DetectEngineThreadCtx *det_ctx,
     SCEnter();
 
     const MpmCtx *mpm_ctx = (MpmCtx *)pectx;
-    DCERPCState *dcerpc_state = DetectDceGetState(f->alproto, f->alstate);
-    if (dcerpc_state == NULL)
-        return;
-
-    uint32_t buffer_len = dcerpc_state->dcerpc.dcerpcrequest.stub_data_buffer_len;
-    const uint8_t *buffer = dcerpc_state->dcerpc.dcerpcrequest.stub_data_buffer;
+    uint8_t *buffer;
+    uint32_t buffer_len;
+
+#ifdef HAVE_RUST
+    if (f->alproto == ALPROTO_SMB) {
+        if (rs_smb_tx_get_stub_data(txv, STREAM_TOSERVER, &buffer, &buffer_len) != 1) {
+            SCLogDebug("have no data!");
+            return;
+        }
+        SCLogDebug("have data!");
+    } else
+#endif
+    {
+        DCERPCState *dcerpc_state = DetectDceGetState(f->alproto, f->alstate);
+        if (dcerpc_state == NULL)
+            return;
 
+        buffer_len = dcerpc_state->dcerpc.dcerpcrequest.stub_data_buffer_len;
+        buffer = dcerpc_state->dcerpc.dcerpcrequest.stub_data_buffer;
+    }
     if (buffer_len >= mpm_ctx->minlen) {
         (void)mpm_table[mpm_ctx->mpm_type].Search(mpm_ctx,
                 &det_ctx->mtcu, &det_ctx->pmq, buffer, buffer_len);
@@ -122,13 +140,26 @@ static void PrefilterTxDceStubDataResponse(DetectEngineThreadCtx *det_ctx,
     SCEnter();
 
     const MpmCtx *mpm_ctx = (MpmCtx *)pectx;
+    uint8_t *buffer;
+    uint32_t buffer_len;
+
+#ifdef HAVE_RUST
+    if (f->alproto == ALPROTO_SMB) {
+        if (rs_smb_tx_get_stub_data(txv, STREAM_TOCLIENT, &buffer, &buffer_len) != 1) {
+            SCLogDebug("have no data!");
+            return;
+        }
+        SCLogDebug("have data!");
+    } else
+#endif
+    {
+        DCERPCState *dcerpc_state = DetectDceGetState(f->alproto, f->alstate);
+        if (dcerpc_state == NULL)
+            return;
 
-    DCERPCState *dcerpc_state = DetectDceGetState(f->alproto, f->alstate);
-    if (dcerpc_state == NULL)
-        return;
-
-    uint32_t buffer_len = dcerpc_state->dcerpc.dcerpcresponse.stub_data_buffer_len;
-    const uint8_t *buffer = dcerpc_state->dcerpc.dcerpcresponse.stub_data_buffer;
+        buffer_len = dcerpc_state->dcerpc.dcerpcresponse.stub_data_buffer_len;
+        buffer = dcerpc_state->dcerpc.dcerpcresponse.stub_data_buffer;
+    }
 
     if (buffer_len >= mpm_ctx->minlen) {
         (void)mpm_table[mpm_ctx->mpm_type].Search(mpm_ctx,
@@ -159,19 +190,29 @@ static int InspectEngineDceStubData(ThreadVars *tv,
 {
     uint32_t buffer_len = 0;
     uint8_t *buffer = NULL;
+    DCERPCState *dcerpc_state = NULL;
 
-    DCERPCState *dcerpc_state = DetectDceGetState(f->alproto, f->alstate);
-    if (dcerpc_state == NULL)
-        goto end;
-
-    if (flags & STREAM_TOSERVER) {
-        buffer_len = dcerpc_state->dcerpc.dcerpcrequest.stub_data_buffer_len;
-        buffer = dcerpc_state->dcerpc.dcerpcrequest.stub_data_buffer;
-    } else if (flags & STREAM_TOCLIENT) {
-        buffer_len = dcerpc_state->dcerpc.dcerpcresponse.stub_data_buffer_len;
-        buffer = dcerpc_state->dcerpc.dcerpcresponse.stub_data_buffer;
+#ifdef HAVE_RUST
+    if (f->alproto == ALPROTO_SMB) {
+        uint8_t dir = flags & (STREAM_TOSERVER|STREAM_TOCLIENT);
+        if (rs_smb_tx_get_stub_data(tx, dir, &buffer, &buffer_len) != 1)
+            goto end;
+        SCLogDebug("have data!");
+    } else
+#endif
+    {
+        dcerpc_state = DetectDceGetState(f->alproto, f->alstate);
+        if (dcerpc_state == NULL)
+            goto end;
+
+        if (flags & STREAM_TOSERVER) {
+            buffer_len = dcerpc_state->dcerpc.dcerpcrequest.stub_data_buffer_len;
+            buffer = dcerpc_state->dcerpc.dcerpcrequest.stub_data_buffer;
+        } else if (flags & STREAM_TOCLIENT) {
+            buffer_len = dcerpc_state->dcerpc.dcerpcresponse.stub_data_buffer_len;
+            buffer = dcerpc_state->dcerpc.dcerpcresponse.stub_data_buffer;
+        }
     }
-
     if (buffer == NULL ||buffer_len == 0)
         goto end;
 
@@ -239,8 +280,8 @@ void DetectDceStubDataRegister(void)
 
 static int DetectDceStubDataSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg)
 {
-    if (DetectSignatureSetAppProto(s, ALPROTO_DCERPC) != 0)
-        return -1;
+//    if (DetectSignatureSetAppProto(s, ALPROTO_DCERPC) != 0)
+//        return -1;
 
     s->init_data->list = g_dce_stub_data_buffer_id;
     return 0;
index 81c37405aeae654df2a102ea21bd1c5b64c939a0..f13b560d0fc877f3e82de5653b1366311b60fb45 100644 (file)
@@ -68,6 +68,8 @@
 #include "detect-engine-event.h"
 #include "decode.h"
 
+#include "detect-smb-share.h"
+
 #include "detect-base64-decode.h"
 #include "detect-base64-data.h"
 #include "detect-ipopts.h"
@@ -457,6 +459,8 @@ void SigTableSetup(void)
     DetectDceIfaceRegister();
     DetectDceOpnumRegister();
     DetectDceStubDataRegister();
+    DetectSmbNamedPipeRegister();
+    DetectSmbShareRegister();
     DetectTlsRegister();
     DetectTlsValidityRegister();
     DetectTlsVersionRegister();
index a5761a501beef4adffe0b92557ca0409975b9d57..a780fbc9bf7645ce58538475868f075df01f8f3a 100644 (file)
@@ -153,6 +153,8 @@ enum {
     DETECT_DCE_IFACE,
     DETECT_DCE_OPNUM,
     DETECT_DCE_STUB_DATA,
+    DETECT_SMB_NAMED_PIPE,
+    DETECT_SMB_SHARE,
 
     DETECT_ASN1,
 
index 51a9046717c16344765c2563a21a9e8321cc9e4b..3d7f370748a030f45477c390429363b2ed51ae0b 100644 (file)
@@ -75,6 +75,14 @@ void DetectFiledataRegister(void)
             PrefilterGenericMpmRegister,
             HttpServerBodyGetDataCallback,
             ALPROTO_HTTP, HTP_RESPONSE_BODY);
+#ifdef HAVE_RUST
+    DetectAppLayerMpmRegister2("file_data", SIG_FLAG_TOSERVER, 2,
+            PrefilterMpmFiledataRegister, NULL,
+            ALPROTO_SMB, 0);
+    DetectAppLayerMpmRegister2("file_data", SIG_FLAG_TOCLIENT, 2,
+            PrefilterMpmFiledataRegister, NULL,
+            ALPROTO_SMB, 0);
+#endif
 
     DetectAppLayerInspectEngineRegister2("file_data",
             ALPROTO_HTTP, SIG_FLAG_TOCLIENT, HTP_RESPONSE_BODY,
@@ -84,6 +92,14 @@ void DetectFiledataRegister(void)
             DetectEngineInspectFiledata, NULL);
     DetectBufferTypeRegisterSetupCallback("file_data",
             DetectFiledataSetupCallback);
+#ifdef HAVE_RUST
+    DetectAppLayerInspectEngineRegister2("file_data",
+            ALPROTO_SMB, SIG_FLAG_TOSERVER, 0,
+            DetectEngineInspectFiledata, NULL);
+    DetectAppLayerInspectEngineRegister2("file_data",
+            ALPROTO_SMB, SIG_FLAG_TOCLIENT, 0,
+            DetectEngineInspectFiledata, NULL);
+#endif
 
     DetectBufferTypeSetDescriptionByName("file_data",
             "http response body or smtp attachments data");
@@ -133,7 +149,7 @@ static int DetectFiledataSetup (DetectEngineCtx *de_ctx, Signature *s, const cha
 
     if (!DetectProtoContainsProto(&s->proto, IPPROTO_TCP) ||
         (s->alproto != ALPROTO_UNKNOWN && s->alproto != ALPROTO_HTTP &&
-        s->alproto != ALPROTO_SMTP)) {
+        s->alproto != ALPROTO_SMTP && s->alproto != ALPROTO_SMB)) {
         SCLogError(SC_ERR_CONFLICTING_RULE_KEYWORDS, "rule contains conflicting keywords.");
         return -1;
     }
index 6cf9c2b855273627d11f2dc9d6b7353c43c9353f..d39e661e74d7265fafff0aecf6f6aa91705563fc 100644 (file)
@@ -99,6 +99,13 @@ void DetectFilenameRegister(void)
             ALPROTO_FTPDATA, SIG_FLAG_TOCLIENT, 0,
             DetectFileInspectGeneric);
 
+    DetectAppLayerInspectEngineRegister("files",
+            ALPROTO_SMB, SIG_FLAG_TOSERVER, 0,
+            DetectFileInspectGeneric);
+    DetectAppLayerInspectEngineRegister("files",
+            ALPROTO_SMB, SIG_FLAG_TOCLIENT, 0,
+            DetectFileInspectGeneric);
+
     g_file_match_list_id = DetectBufferTypeGetByName("files");
 
        SCLogDebug("registering filename rule option");
diff --git a/src/detect-smb-share.c b/src/detect-smb-share.c
new file mode 100644 (file)
index 0000000..b222b02
--- /dev/null
@@ -0,0 +1,243 @@
+/* Copyright (C) 2017 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Victor Julien <victor@inliniac.net>
+ *
+ */
+
+#include "suricata-common.h"
+
+#include "detect.h"
+#include "detect-parse.h"
+
+#include "detect-engine.h"
+#include "detect-engine-mpm.h"
+#include "detect-engine-state.h"
+#include "detect-engine-prefilter.h"
+#include "detect-engine-content-inspection.h"
+
+#include "detect-smb-share.h"
+#ifdef HAVE_RUST
+#include "rust.h"
+#include "rust-smb-detect-gen.h"
+
+#define BUFFER_NAME "smb_named_pipe"
+#define KEYWORD_NAME BUFFER_NAME
+#define KEYWORD_ID DETECT_SMB_NAMED_PIPE
+
+static int g_smb_named_pipe_buffer_id = 0;
+
+/** \brief SMB NAMED PIPE Mpm prefilter callback
+ *
+ *  \param det_ctx detection engine thread ctx
+ *  \param p packet to inspect
+ *  \param f flow to inspect
+ *  \param txv tx to inspect
+ *  \param pectx inspection context
+ */
+static void PrefilterTxSmbNamedPipe(DetectEngineThreadCtx *det_ctx,
+        const void *pectx,
+        Packet *p, Flow *f, void *txv,
+        const uint64_t idx, const uint8_t flags)
+{
+    SCEnter();
+
+    const MpmCtx *mpm_ctx = (MpmCtx *)pectx;
+    uint8_t *buffer;
+    uint32_t buffer_len;
+
+    if (rs_smb_tx_get_named_pipe(txv, &buffer, &buffer_len) != 1) {
+        return;
+    }
+
+    if (buffer_len >= mpm_ctx->minlen) {
+        (void)mpm_table[mpm_ctx->mpm_type].Search(mpm_ctx,
+                &det_ctx->mtcu, &det_ctx->pmq, buffer, buffer_len);
+    }
+}
+
+static int PrefilterTxSmbNamedPipeRequestRegister(DetectEngineCtx *de_ctx,
+        SigGroupHead *sgh, MpmCtx *mpm_ctx)
+{
+    SCEnter();
+
+    return PrefilterAppendTxEngine(de_ctx, sgh, PrefilterTxSmbNamedPipe,
+        ALPROTO_SMB, 0,
+        mpm_ctx, NULL, KEYWORD_NAME " (request)");
+}
+
+static int InspectEngineSmbNamedPipe(ThreadVars *tv,
+        DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+        const Signature *s, const SigMatchData *smd,
+        Flow *f, uint8_t flags, void *alstate, void *tx, uint64_t tx_id)
+{
+    uint32_t buffer_len = 0;
+    uint8_t *buffer = NULL;
+
+    if (rs_smb_tx_get_named_pipe(tx, &buffer, &buffer_len) != 1)
+        goto end;
+    if (buffer == NULL ||buffer_len == 0)
+        goto end;
+
+    det_ctx->buffer_offset = 0;
+    det_ctx->discontinue_matching = 0;
+    det_ctx->inspection_recursion_counter = 0;
+    int r = DetectEngineContentInspection(de_ctx, det_ctx, s, smd,
+                                          f,
+                                          buffer, buffer_len,
+                                          0, DETECT_CI_FLAGS_SINGLE,
+                                          DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE,
+                                          NULL);
+    if (r == 1)
+        return DETECT_ENGINE_INSPECT_SIG_MATCH;
+
+end:
+    return DETECT_ENGINE_INSPECT_SIG_NO_MATCH;
+}
+
+static int DetectSmbNamedPipeSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg)
+{
+    s->init_data->list = g_smb_named_pipe_buffer_id;
+    return 0;
+}
+
+void DetectSmbNamedPipeRegister(void)
+{
+    sigmatch_table[KEYWORD_ID].name = KEYWORD_NAME;
+    sigmatch_table[KEYWORD_ID].Setup = DetectSmbNamedPipeSetup;
+    sigmatch_table[KEYWORD_ID].flags |= SIGMATCH_NOOPT;
+
+    DetectAppLayerMpmRegister(BUFFER_NAME, SIG_FLAG_TOSERVER, 2,
+            PrefilterTxSmbNamedPipeRequestRegister);
+
+    DetectAppLayerInspectEngineRegister(BUFFER_NAME,
+            ALPROTO_SMB, SIG_FLAG_TOSERVER, 0,
+            InspectEngineSmbNamedPipe);
+
+    g_smb_named_pipe_buffer_id = DetectBufferTypeGetByName(BUFFER_NAME);
+}
+
+#undef BUFFER_NAME
+#undef KEYWORD_NAME
+#undef KEYWORD_ID
+
+#else /* NO RUST */
+void DetectSmbNamedPipeRegister(void) {}
+#endif
+
+#ifdef HAVE_RUST
+#define BUFFER_NAME "smb_share"
+#define KEYWORD_NAME BUFFER_NAME
+#define KEYWORD_ID DETECT_SMB_SHARE
+
+static int g_smb_share_buffer_id = 0;
+
+/** \brief SMB SHARE Mpm prefilter callback
+ *
+ *  \param det_ctx detection engine thread ctx
+ *  \param p packet to inspect
+ *  \param f flow to inspect
+ *  \param txv tx to inspect
+ *  \param pectx inspection context
+ */
+static void PrefilterTxSmbShare(DetectEngineThreadCtx *det_ctx,
+        const void *pectx,
+        Packet *p, Flow *f, void *txv,
+        const uint64_t idx, const uint8_t flags)
+{
+    SCEnter();
+
+    const MpmCtx *mpm_ctx = (MpmCtx *)pectx;
+    uint8_t *buffer;
+    uint32_t buffer_len;
+
+    if (rs_smb_tx_get_share(txv, &buffer, &buffer_len) != 1) {
+        return;
+    }
+
+    if (buffer_len >= mpm_ctx->minlen) {
+        (void)mpm_table[mpm_ctx->mpm_type].Search(mpm_ctx,
+                &det_ctx->mtcu, &det_ctx->pmq, buffer, buffer_len);
+    }
+}
+
+static int PrefilterTxSmbShareRequestRegister(DetectEngineCtx *de_ctx,
+        SigGroupHead *sgh, MpmCtx *mpm_ctx)
+{
+    SCEnter();
+
+    return PrefilterAppendTxEngine(de_ctx, sgh, PrefilterTxSmbShare,
+        ALPROTO_SMB, 0,
+        mpm_ctx, NULL, KEYWORD_NAME " (request)");
+}
+
+static int InspectEngineSmbShare(ThreadVars *tv,
+        DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+        const Signature *s, const SigMatchData *smd,
+        Flow *f, uint8_t flags, void *alstate, void *tx, uint64_t tx_id)
+{
+    uint32_t buffer_len = 0;
+    uint8_t *buffer = NULL;
+
+    if (rs_smb_tx_get_share(tx, &buffer, &buffer_len) != 1)
+        goto end;
+    if (buffer == NULL ||buffer_len == 0)
+        goto end;
+
+    det_ctx->buffer_offset = 0;
+    det_ctx->discontinue_matching = 0;
+    det_ctx->inspection_recursion_counter = 0;
+    int r = DetectEngineContentInspection(de_ctx, det_ctx, s, smd,
+                                          f,
+                                          buffer, buffer_len,
+                                          0, DETECT_CI_FLAGS_SINGLE,
+                                          DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE,
+                                          NULL);
+    if (r == 1)
+        return DETECT_ENGINE_INSPECT_SIG_MATCH;
+
+end:
+    return DETECT_ENGINE_INSPECT_SIG_NO_MATCH;
+}
+
+static int DetectSmbShareSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg)
+{
+    s->init_data->list = g_smb_share_buffer_id;
+    return 0;
+}
+
+void DetectSmbShareRegister(void)
+{
+    sigmatch_table[KEYWORD_ID].name = KEYWORD_NAME;
+    sigmatch_table[KEYWORD_ID].Setup = DetectSmbShareSetup;
+    sigmatch_table[KEYWORD_ID].flags |= SIGMATCH_NOOPT;
+
+    DetectAppLayerMpmRegister(BUFFER_NAME, SIG_FLAG_TOSERVER, 2,
+            PrefilterTxSmbShareRequestRegister);
+
+    DetectAppLayerInspectEngineRegister(BUFFER_NAME,
+            ALPROTO_SMB, SIG_FLAG_TOSERVER, 0,
+            InspectEngineSmbShare);
+
+    g_smb_share_buffer_id = DetectBufferTypeGetByName(BUFFER_NAME);
+}
+#else
+void DetectSmbShareRegister(void) {}
+#endif
diff --git a/src/detect-smb-share.h b/src/detect-smb-share.h
new file mode 100644 (file)
index 0000000..a5e5be9
--- /dev/null
@@ -0,0 +1,30 @@
+/* Copyright (C) 2017 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Victor Julien <victor@inliniac.net>
+ */
+
+#ifndef __DETECT_SMB_NAMED_PIPE_H__
+#define __DETECT_SMB_NAMED_PIPE_H__
+
+void DetectSmbNamedPipeRegister(void);
+void DetectSmbShareRegister(void);
+
+#endif /* __DETECT_SMB_NAMED_PIPE_H__ */
index 59d818bc45df3ae033d648cad09b2365078f231b..5013171befa01204cdcbd490d2b357952860d341 100644 (file)
@@ -63,6 +63,7 @@
 #include "output-json-smtp.h"
 #include "output-json-email-common.h"
 #include "output-json-nfs.h"
+#include "output-json-smb.h"
 #include "output-json-flow.h"
 
 #include "util-byte.h"
@@ -460,6 +461,10 @@ static int AlertJson(ThreadVars *tv, JsonAlertLogThread *aft, const Packet *p)
                 hjs = JsonNFSAddMetadata(p->flow, pa->tx_id);
                 if (hjs)
                     json_object_set_new(js, "nfs", hjs);
+            } else if (proto == ALPROTO_SMB) {
+                hjs = JsonSMBAddMetadata(p->flow, pa->tx_id);
+                if (hjs)
+                    json_object_set_new(js, "smb", hjs);
             }
 #endif
             if (proto == ALPROTO_FTPDATA) {
diff --git a/src/output-json-smb.c b/src/output-json-smb.c
new file mode 100644 (file)
index 0000000..4be4376
--- /dev/null
@@ -0,0 +1,208 @@
+/* Copyright (C) 2017 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Victor Julien <victor@inliniac.net>
+ *
+ * Implement JSON/eve logging app-layer SMB.
+ */
+
+#include "suricata-common.h"
+#include "debug.h"
+#include "pkt-var.h"
+#include "conf.h"
+
+#include "threads.h"
+#include "threadvars.h"
+#include "tm-threads.h"
+
+#include "util-unittest.h"
+#include "util-buffer.h"
+#include "util-debug.h"
+#include "util-byte.h"
+
+#include "output.h"
+#include "output-json.h"
+
+#include "app-layer.h"
+#include "app-layer-parser.h"
+
+#include "output-json-smb.h"
+
+#ifdef HAVE_RUST
+#ifdef HAVE_LIBJANSSON
+#include "rust.h"
+#include "rust-smb-log-gen.h"
+
+typedef struct LogSMBFileCtx_ {
+    LogFileCtx *file_ctx;
+    uint32_t    flags;
+} LogSMBFileCtx;
+
+typedef struct LogSMBLogThread_ {
+    LogSMBFileCtx *smblog_ctx;
+    uint32_t            count;
+    MemBuffer          *buffer;
+} LogSMBLogThread;
+
+json_t *JsonSMBAddMetadata(const Flow *f, uint64_t tx_id)
+{
+    SMBState *state = FlowGetAppState(f);
+    if (state) {
+        SMBTransaction *tx = AppLayerParserGetTx(f->proto, ALPROTO_SMB, state, tx_id);
+        if (tx) {
+            return rs_smb_log_json_response(state, tx);
+        }
+    }
+
+    return NULL;
+}
+
+static int JsonSMBLogger(ThreadVars *tv, void *thread_data,
+    const Packet *p, Flow *f, void *state, void *tx, uint64_t tx_id)
+{
+    //SMBTransaction *smbtx = tx;
+    LogSMBLogThread *thread = thread_data;
+    json_t *js, *smbjs;
+
+    js = CreateJSONHeader((Packet *)p, 0, "smb");
+    if (unlikely(js == NULL)) {
+        return TM_ECODE_FAILED;
+    }
+
+    smbjs = rs_smb_log_json_response(state, tx);
+    if (unlikely(smbjs == NULL)) {
+        goto error;
+    }
+    json_object_set_new(js, "smb", smbjs);
+
+    MemBufferReset(thread->buffer);
+    OutputJSONBuffer(js, thread->smblog_ctx->file_ctx, &thread->buffer);
+
+    json_decref(js);
+    return TM_ECODE_OK;
+
+error:
+    json_decref(js);
+    return TM_ECODE_FAILED;
+}
+
+static void OutputSMBLogDeInitCtxSub(OutputCtx *output_ctx)
+{
+    LogSMBFileCtx *smblog_ctx = (LogSMBFileCtx *)output_ctx->data;
+    SCFree(smblog_ctx);
+    SCFree(output_ctx);
+}
+
+static OutputInitResult OutputSMBLogInitSub(ConfNode *conf,
+    OutputCtx *parent_ctx)
+{
+    OutputInitResult result = { NULL, false };
+    OutputJsonCtx *ajt = parent_ctx->data;
+
+    LogSMBFileCtx *smblog_ctx = SCCalloc(1, sizeof(*smblog_ctx));
+    if (unlikely(smblog_ctx == NULL)) {
+        return result;
+    }
+    smblog_ctx->file_ctx = ajt->file_ctx;
+
+    OutputCtx *output_ctx = SCCalloc(1, sizeof(*output_ctx));
+    if (unlikely(output_ctx == NULL)) {
+        SCFree(smblog_ctx);
+        return result;
+    }
+    output_ctx->data = smblog_ctx;
+    output_ctx->DeInit = OutputSMBLogDeInitCtxSub;
+
+    SCLogDebug("SMB log sub-module initialized.");
+
+    AppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_SMB);
+    AppLayerParserRegisterLogger(IPPROTO_UDP, ALPROTO_SMB);
+
+    result.ctx = output_ctx;
+    result.ok = true;
+    return result;
+}
+
+#define OUTPUT_BUFFER_SIZE 65535
+
+static TmEcode JsonSMBLogThreadInit(ThreadVars *t, const void *initdata, void **data)
+{
+    LogSMBLogThread *thread = SCCalloc(1, sizeof(*thread));
+    if (unlikely(thread == NULL)) {
+        return TM_ECODE_FAILED;
+    }
+
+    if (initdata == NULL) {
+        SCLogDebug("Error getting context for EveLogSMB.  \"initdata\" is NULL.");
+        SCFree(thread);
+        return TM_ECODE_FAILED;
+    }
+
+    thread->buffer = MemBufferCreateNew(OUTPUT_BUFFER_SIZE);
+    if (unlikely(thread->buffer == NULL)) {
+        SCFree(thread);
+        return TM_ECODE_FAILED;
+    }
+
+    thread->smblog_ctx = ((OutputCtx *)initdata)->data;
+    *data = (void *)thread;
+
+    return TM_ECODE_OK;
+}
+
+static TmEcode JsonSMBLogThreadDeinit(ThreadVars *t, void *data)
+{
+    LogSMBLogThread *thread = (LogSMBLogThread *)data;
+    if (thread == NULL) {
+        return TM_ECODE_OK;
+    }
+    if (thread->buffer != NULL) {
+        MemBufferFree(thread->buffer);
+    }
+    SCFree(thread);
+    return TM_ECODE_OK;
+}
+
+void JsonSMBLogRegister(void)
+{
+    /* Register as an eve sub-module. */
+    OutputRegisterTxSubModule(LOGGER_JSON_SMB, "eve-log", "JsonSMBLog",
+        "eve-log.smb", OutputSMBLogInitSub, ALPROTO_SMB,
+        JsonSMBLogger, JsonSMBLogThreadInit,
+        JsonSMBLogThreadDeinit, NULL);
+
+    SCLogDebug("SMB JSON logger registered.");
+}
+
+#else /* No JSON support. */
+
+void JsonSMBLogRegister(void)
+{
+}
+
+#endif /* HAVE_LIBJANSSON */
+
+#else /* no rust */
+
+void JsonSMBLogRegister(void)
+{
+}
+
+#endif /* HAVE_RUST */
diff --git a/src/output-json-smb.h b/src/output-json-smb.h
new file mode 100644 (file)
index 0000000..6f96002
--- /dev/null
@@ -0,0 +1,31 @@
+/* Copyright (C) 2017 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Victor Julien <victor@inliniac.net>
+ */
+
+#ifndef __OUTPUT_JSON_SMB_H__
+#define __OUTPUT_JSON_SMB_H__
+
+void JsonSMBLogRegister(void);
+#ifdef HAVE_RUST
+json_t *JsonSMBAddMetadata(const Flow *f, uint64_t tx_id);
+#endif /* HAVE_RUST */
+#endif /* __OUTPUT_JSON_SMB_H__ */
index 663aac53868968872817054e5b651ece8bf92e7a..65f3e7251f4ae3eee1e47b03d9a80c8d61c55c0a 100644 (file)
@@ -70,6 +70,7 @@
 #include "output-json.h"
 #include "output-json-nfs.h"
 #include "output-json-tftp.h"
+#include "output-json-smb.h"
 #include "output-json-template.h"
 #include "output-lua.h"
 #include "output-json-dnp3.h"
@@ -1092,6 +1093,9 @@ void OutputRegisterLoggers(void)
     JsonNFSLogRegister();
     /* TFTP JSON logger. */
     JsonTFTPLogRegister();
+    /* SMB JSON logger. */
+    JsonSMBLogRegister();
+
     /* Template JSON logger. */
     JsonTemplateLogRegister();
 }
index c016fd493be63e877666d959638e557ca21fcb33..151dc89f7d1e2f9641f9b5af5b5d9609a641373d 100644 (file)
@@ -53,5 +53,7 @@ typedef struct _Store Store;
 /** Opaque Rust types. */
 typedef struct NFState_ NFSState;
 typedef struct NFSTransaction_ NFSTransaction;
+typedef struct SMBState_ SMBState;
+typedef struct SMBTransaction_ SMBTransaction;
 
 #endif /* !__RUST_H__ */
index beeee12395d28d924db14064af2c7c394125d380..eb4be159bf012207741b33519525cf3980293afb 100644 (file)
@@ -410,6 +410,7 @@ typedef enum {
     LOGGER_JSON_DNP3_TS,
     LOGGER_JSON_DNP3_TC,
     LOGGER_JSON_SSH,
+    LOGGER_JSON_SMB,
     LOGGER_JSON_TEMPLATE,
 
     LOGGER_ALERT_DEBUG,
index 3f8209746bb98a2470089001e3691e9297d1f239..354c4f80c26737645a970d6dbd4e92a89bda684b 100644 (file)
@@ -347,6 +347,7 @@ const char * SCErrorToString(SCError err)
         CASE_CODE (SC_ERR_PF_RING_VLAN);
         CASE_CODE (SC_ERR_CREATE_DIRECTORY);
         CASE_CODE (SC_WARN_FLOWBIT);
+        CASE_CODE (SC_ERR_SMB_CONFIG);
 
         CASE_CODE (SC_ERR_MAX);
     }
index 94f0a31f1e8d5732f785a97b0d23e2d529232d4a..d3fac31545cc70120921694619b4d8ee46c0ece6 100644 (file)
@@ -337,6 +337,7 @@ typedef enum {
     SC_ERR_PF_RING_VLAN,
     SC_ERR_CREATE_DIRECTORY,
     SC_WARN_FLOWBIT,
+    SC_ERR_SMB_CONFIG,
 
     SC_ERR_MAX,
 } SCError;
index 01b22dfe4047c18cd95290db662c2afbf885c732..70a44c3d6251e7c76882c0e2d051a3cac4359f94 100644 (file)
@@ -1244,6 +1244,7 @@ const char * PacketProfileLoggertIdToString(LoggerId id)
         CASE_CODE (LOGGER_JSON_SSH);
         CASE_CODE (LOGGER_DNS_TS);
         CASE_CODE (LOGGER_DNS_TC);
+        CASE_CODE (LOGGER_JSON_SMB);
         CASE_CODE (LOGGER_HTTP);
         CASE_CODE (LOGGER_JSON_DNS_TS);
         CASE_CODE (LOGGER_JSON_DNS_TC);
index 6d6f6b5b1362a2ce87be71cb0daae905bebdb639..0bcaafa708d9c96a9fa48b3cf6ccd4bb3be7f7b2 100644 (file)
@@ -167,6 +167,8 @@ outputs:
       # Include top level metadata. Default yes.
       #metadata: no
 
+      pcap-file: false
+
       types:
         - alert:
             # payload: yes             # enable dumping payload in Base64
@@ -798,13 +800,12 @@ app-layer:
       enabled: detection-only
     msn:
       enabled: detection-only
+    # Note: --enable-rust is required for full SMB1/2 support. W/o rust
+    # only minimal SMB1 support is available.
     smb:
       enabled: yes
       detection-ports:
         dp: 139, 445
-    # smb2 detection is disabled internally inside the engine.
-    #smb2:
-    #  enabled: yes
     # Note: NFS parser depends on Rust support: pass --enable-rust
     # to configure.
     nfs: