use std::str;
use std::string::String;
use uuid;
+use suricata_sys::sys::SCConfNode;
+use crate::conf::ConfNode;
use crate::jsonbuilder::{JsonBuilder, JsonError};
use crate::smb::smb::*;
use crate::smb::smb1::*;
use crate::dcerpc::dcerpc::*;
use crate::smb::funcs::*;
use crate::smb::smb_status::*;
+use std::error::Error;
+use std::fmt;
#[cfg(not(feature = "debug"))]
fn debug_add_progress(_js: &mut JsonBuilder, _tx: &SMBTransaction) -> Result<(), JsonError> { Ok(()) }
}
}
-fn smb_common_header(jsb: &mut JsonBuilder, state: &SMBState, tx: &SMBTransaction) -> Result<(), JsonError>
-{
+// Wrapping error for either jsonbuilder error or our own custom error if
+// tx is not to be logged due to config
+#[derive(Debug)]
+enum SmbLogError {
+ SkippedByConf,
+ Json(JsonError),
+}
+
+impl Error for SmbLogError {}
+
+impl fmt::Display for SmbLogError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ SmbLogError::SkippedByConf => {
+ write!(f, "skipped by configuration")
+ }
+ SmbLogError::Json(j) => j.fmt(f),
+ }
+ }
+}
+
+impl From<JsonError> for SmbLogError {
+ fn from(err: JsonError) -> SmbLogError {
+ SmbLogError::Json(err)
+ }
+}
+
+fn smb_common_header(
+ jsb: &mut JsonBuilder, state: &SMBState, tx: &SMBTransaction, flags: u64,
+) -> Result<(), SmbLogError> {
jsb.set_uint("id", tx.id)?;
if state.dialect != 0 {
match tx.type_data {
Some(SMBTransactionTypeData::SESSIONSETUP(ref x)) => {
+ if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_SESSIONSETUP) == 0 {
+ return Err(SmbLogError::SkippedByConf);
+ }
if let Some(ref ntlmssp) = x.ntlmssp {
jsb.open_object("ntlmssp")?;
let domain = String::from_utf8_lossy(&ntlmssp.domain);
}
},
Some(SMBTransactionTypeData::CREATE(ref x)) => {
+ if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_CREATE) == 0 {
+ return Err(SmbLogError::SkippedByConf);
+ }
let mut name_raw = x.filename.to_vec();
name_raw.retain(|&i|i != 0x00);
if !name_raw.is_empty() {
jsb.set_string("fuid", &gs)?;
},
Some(SMBTransactionTypeData::NEGOTIATE(ref x)) => {
+ if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_NEGOTIATE) == 0 {
+ return Err(SmbLogError::SkippedByConf);
+ }
if x.smb_ver == 1 {
jsb.open_array("client_dialects")?;
for d in &x.dialects {
}
},
Some(SMBTransactionTypeData::TREECONNECT(ref x)) => {
+ if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_TREECONNECT) == 0 {
+ return Err(SmbLogError::SkippedByConf);
+ }
let share_name = String::from_utf8_lossy(&x.share_name);
if x.is_pipe {
jsb.set_string("named_pipe", &share_name)?;
}
},
Some(SMBTransactionTypeData::FILE(ref x)) => {
+ if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_FILE) == 0 {
+ return Err(SmbLogError::SkippedByConf);
+ }
let file_name = String::from_utf8_lossy(&x.file_name);
jsb.set_string("filename", &file_name)?;
let share_name = String::from_utf8_lossy(&x.share_name);
jsb.set_string("fuid", &gs)?;
},
Some(SMBTransactionTypeData::RENAME(ref x)) => {
+ if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_RENAME) == 0 {
+ return Err(SmbLogError::SkippedByConf);
+ }
if tx.vercmd.get_version() == 2 {
jsb.open_object("set_info")?;
jsb.set_string("class", "FILE_INFO")?;
jsb.set_string("fuid", &gs)?;
},
Some(SMBTransactionTypeData::DCERPC(ref x)) => {
+ if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_DCERPC) == 0 {
+ return Err(SmbLogError::SkippedByConf);
+ }
jsb.open_object("dcerpc")?;
if x.req_set {
jsb.set_string("request", &dcerpc_type_string(x.req_cmd))?;
jsb.close()?;
}
Some(SMBTransactionTypeData::IOCTL(ref x)) => {
+ if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_IOCTL) == 0 {
+ return Err(SmbLogError::SkippedByConf);
+ }
jsb.set_string("function", &fsctl_func_to_string(x.func))?;
},
Some(SMBTransactionTypeData::SETFILEPATHINFO(ref x)) => {
+ if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_SETFILEPATHINFO) == 0 {
+ return Err(SmbLogError::SkippedByConf);
+ }
let mut name_raw = x.filename.to_vec();
name_raw.retain(|&i|i != 0x00);
if !name_raw.is_empty() {
let gs = fuid_to_string(&x.fid);
jsb.set_string("fuid", &gs)?;
},
- _ => { },
+ None => {
+ if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_GENERIC) == 0 {
+ return Err(SmbLogError::SkippedByConf);
+ }
+ },
}
return Ok(());
}
#[no_mangle]
-pub extern "C" fn SCSmbLogJsonResponse(jsb: &mut JsonBuilder, state: &mut SMBState, tx: &SMBTransaction) -> bool
-{
- smb_common_header(jsb, state, tx).is_ok()
+pub extern "C" fn SCSmbLogJsonResponse(
+ jsb: &mut JsonBuilder, state: &mut SMBState, tx: &SMBTransaction, flags: u64,
+) -> bool {
+ smb_common_header(jsb, state, tx, flags).is_ok()
+}
+
+// Flag constants for logging types
+const SMB_LOG_TYPE_FILE: u64 = BIT_U64!(0);
+const SMB_LOG_TYPE_TREECONNECT: u64 = BIT_U64!(1);
+const SMB_LOG_TYPE_NEGOTIATE: u64 = BIT_U64!(2);
+const SMB_LOG_TYPE_DCERPC: u64 = BIT_U64!(3);
+const SMB_LOG_TYPE_CREATE: u64 = BIT_U64!(4);
+const SMB_LOG_TYPE_SESSIONSETUP: u64 = BIT_U64!(5);
+const SMB_LOG_TYPE_IOCTL: u64 = BIT_U64!(6);
+const SMB_LOG_TYPE_RENAME: u64 = BIT_U64!(7);
+const SMB_LOG_TYPE_SETFILEPATHINFO: u64 = BIT_U64!(8);
+const SMB_LOG_TYPE_GENERIC: u64 = BIT_U64!(9);
+const SMB_LOG_DEFAULT_ALL: u64 = 0;
+
+fn get_smb_log_type_from_str(s: &str) -> Option<u64> {
+ match s {
+ "file" => Some(SMB_LOG_TYPE_FILE),
+ "tree_connect" => Some(SMB_LOG_TYPE_TREECONNECT),
+ "negotiate" => Some(SMB_LOG_TYPE_NEGOTIATE),
+ "dcerpc" => Some(SMB_LOG_TYPE_DCERPC),
+ "create" => Some(SMB_LOG_TYPE_CREATE),
+ "session_setup" => Some(SMB_LOG_TYPE_SESSIONSETUP),
+ "ioctl" => Some(SMB_LOG_TYPE_IOCTL),
+ "rename" => Some(SMB_LOG_TYPE_RENAME),
+ "set_file_path_info" => Some(SMB_LOG_TYPE_SETFILEPATHINFO),
+ "generic" => Some(SMB_LOG_TYPE_GENERIC),
+ _ => None,
+ }
}
+#[no_mangle]
+pub extern "C" fn SCSmbLogParseConfig(conf: *const SCConfNode) -> u64 {
+ let conf = ConfNode::wrap(conf);
+ if let Some(node) = conf.get_child_node("types") {
+ // iterate smb.types list of types
+ let mut r = SMB_LOG_DEFAULT_ALL;
+ let mut node = node.first();
+ loop {
+ if node.is_none() {
+ break;
+ }
+ let nodeu = node.unwrap();
+ if let Some(f) = get_smb_log_type_from_str(nodeu.value()) {
+ r |= f;
+ } else {
+ SCLogWarning!("unknown type for smb logging: {}", nodeu.value());
+ }
+ node = nodeu.next();
+ }
+ if r == SMB_LOG_DEFAULT_ALL {
+ SCLogWarning!("empty types list for smb is interpreted as logging all");
+ }
+ return r;
+ }
+ return SMB_LOG_DEFAULT_ALL;
+}
if (state) {
SMBTransaction *tx = AppLayerParserGetTx(f->proto, ALPROTO_SMB, state, tx_id);
if (tx) {
- return SCSmbLogJsonResponse(jb, state, tx);
+ // flags 0 means log all
+ return SCSmbLogJsonResponse(jb, state, tx, 0);
}
}
return false;
}
+typedef struct LogSmbFileCtx_ {
+ uint64_t flags;
+ // generic context needed for init by CreateEveThreadCtx
+ // comes from parent in SMBLogInitSub
+ OutputJsonCtx *eve_ctx;
+} LogSmbFileCtx;
+
+// wrapper structure
+typedef struct LogSmbLogThread_ {
+ // generic structure
+ OutputJsonThreadCtx *ctx;
+ // smb-specific structure
+ LogSmbFileCtx *smblog_ctx;
+} LogSmbLogThread;
+
static int JsonSMBLogger(ThreadVars *tv, void *thread_data,
const Packet *p, Flow *f, void *state, void *tx, uint64_t tx_id)
{
- OutputJsonThreadCtx *thread = thread_data;
+ LogSmbLogThread *thread = thread_data;
- SCJsonBuilder *jb = CreateEveHeader(p, LOG_DIR_FLOW, "smb", NULL, thread->ctx);
+ SCJsonBuilder *jb = CreateEveHeader(p, LOG_DIR_FLOW, "smb", NULL, thread->ctx->ctx);
if (unlikely(jb == NULL)) {
return TM_ECODE_FAILED;
}
SCJbOpenObject(jb, "smb");
- if (!SCSmbLogJsonResponse(jb, state, tx)) {
+ if (!SCSmbLogJsonResponse(jb, state, tx, thread->smblog_ctx->flags)) {
goto error;
}
SCJbClose(jb);
- OutputJsonBuilderBuffer(tv, p, p->flow, jb, thread);
+ OutputJsonBuilderBuffer(tv, p, p->flow, jb, thread->ctx);
SCJbFree(jb);
return TM_ECODE_OK;
return TM_ECODE_FAILED;
}
+static void LogSmbLogDeInitCtxSub(OutputCtx *output_ctx)
+{
+ LogSmbFileCtx *smblog_ctx = (LogSmbFileCtx *)output_ctx->data;
+ SCFree(smblog_ctx);
+ SCFree(output_ctx);
+}
+
static OutputInitResult SMBLogInitSub(SCConfNode *conf, OutputCtx *parent_ctx)
{
AppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_SMB);
AppLayerParserRegisterLogger(IPPROTO_UDP, ALPROTO_SMB);
- return OutputJsonLogInitSub(conf, parent_ctx);
+ OutputInitResult r = OutputJsonLogInitSub(conf, parent_ctx);
+ if (r.ok) {
+ // generic init is ok, try smb-specific one
+ LogSmbFileCtx *smblog_ctx = SCCalloc(1, sizeof(LogSmbFileCtx));
+ if (unlikely(smblog_ctx == NULL)) {
+ SCFree(r.ctx);
+ r.ctx = NULL;
+ r.ok = false;
+ return r;
+ }
+ smblog_ctx->eve_ctx = parent_ctx->data;
+ // parse config for flags/types to log
+ smblog_ctx->flags = SCSmbLogParseConfig(conf);
+ r.ctx->data = smblog_ctx;
+ r.ctx->DeInit = LogSmbLogDeInitCtxSub;
+ }
+ return r;
+}
+
+static TmEcode LogSmbLogThreadInit(ThreadVars *t, const void *initdata, void **data)
+{
+ if (initdata == NULL) {
+ return TM_ECODE_FAILED;
+ }
+
+ LogSmbLogThread *aft = SCCalloc(1, sizeof(LogSmbLogThread));
+ if (unlikely(aft == NULL)) {
+ return TM_ECODE_FAILED;
+ }
+
+ aft->smblog_ctx = ((OutputCtx *)initdata)->data;
+ aft->ctx = CreateEveThreadCtx(t, aft->smblog_ctx->eve_ctx);
+ if (!aft->ctx) {
+ SCFree(aft);
+ return TM_ECODE_FAILED;
+ }
+
+ *data = (void *)aft;
+ return TM_ECODE_OK;
+}
+
+// LogSmbLogThread structure wraps a generic OutputJsonThreadCtx
+// created by CreateEveThreadCtx
+static TmEcode LogSmbLogThreadDeinit(ThreadVars *t, void *data)
+{
+ LogSmbLogThread *aft = (LogSmbLogThread *)data;
+ TmEcode r = JsonLogThreadDeinit(t, aft->ctx);
+ SCFree(aft);
+ return r;
}
void JsonSMBLogRegister(void)
{
/* Register as an eve sub-module. */
OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonSMBLog", "eve-log.smb", SMBLogInitSub,
- ALPROTO_SMB, JsonSMBLogger, JsonLogThreadInit, JsonLogThreadDeinit);
+ ALPROTO_SMB, JsonSMBLogger, LogSmbLogThreadInit, LogSmbLogThreadDeinit);
SCLogDebug("SMB JSON logger registered.");
}