Skip to content

Commit

Permalink
smtp: use rust for mime parsing
Browse files Browse the repository at this point in the history
Ticket: #3487
  • Loading branch information
catenacyber committed Nov 27, 2023
1 parent c113706 commit 32bf3b4
Show file tree
Hide file tree
Showing 16 changed files with 1,806 additions and 4,965 deletions.
571 changes: 571 additions & 0 deletions rust/src/mime/mime.rs

Large diffs are not rendered by default.

599 changes: 4 additions & 595 deletions rust/src/mime/mod.rs

Large diffs are not rendered by default.

844 changes: 844 additions & 0 deletions rust/src/mime/smtp.rs

Large diffs are not rendered by default.

195 changes: 195 additions & 0 deletions rust/src/mime/smtp_log.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/* Copyright (C) 2022 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 super::mime;
use crate::jsonbuilder::{JsonBuilder, JsonError};
use crate::mime::smtp::{MimeSmtpMd5State, MimeStateSMTP};
use digest::Digest;
use digest::Update;
use md5::Md5;
use std::ffi::CStr;

fn log_subject_md5(js: &mut JsonBuilder, ctx: &mut MimeStateSMTP) -> Result<(), JsonError> {
for h in &ctx.headers[..ctx.main_headers_nb] {
if mime::slice_equals_lowercase(&h.name, b"subject") {
let hash = format!("{:x}", Md5::new().chain(&h.value).finalize());
js.set_string("subject_md5", &hash)?;
break;
}
}
return Ok(());
}

#[no_mangle]
pub unsafe extern "C" fn SCMimeSmtpLogSubjectMd5(
js: &mut JsonBuilder, ctx: &mut MimeStateSMTP,
) -> bool {
return log_subject_md5(js, ctx).is_ok();
}

fn log_body_md5(js: &mut JsonBuilder, ctx: &mut MimeStateSMTP) -> Result<(), JsonError> {
if ctx.md5_state == MimeSmtpMd5State::MimeSmtpMd5Completed {
let hash = format!("{:x}", ctx.md5_result);
js.set_string("body_md5", &hash)?;
}
return Ok(());
}

#[no_mangle]
pub unsafe extern "C" fn SCMimeSmtpLogBodyMd5(
js: &mut JsonBuilder, ctx: &mut MimeStateSMTP,
) -> bool {
return log_body_md5(js, ctx).is_ok();
}

fn log_field_array(
js: &mut JsonBuilder, ctx: &mut MimeStateSMTP, c: &str, e: &str,
) -> Result<(), JsonError> {
let mark = js.get_mark();
let mut found = false;
js.open_array(c)?;

for h in &ctx.headers[..ctx.main_headers_nb] {
if mime::slice_equals_lowercase(&h.name, e.as_bytes()) {
found = true;
js.append_string(&String::from_utf8_lossy(&h.value))?;
}
}

if found {
js.close()?;
} else {
js.restore_mark(&mark)?;
}

return Ok(());
}

#[no_mangle]
pub unsafe extern "C" fn SCMimeSmtpLogFieldArray(
js: &mut JsonBuilder, ctx: &mut MimeStateSMTP, email: *const std::os::raw::c_char,
config: *const std::os::raw::c_char,
) -> bool {
let e: &CStr = CStr::from_ptr(email); //unsafe
if let Ok(email_field) = e.to_str() {
let c: &CStr = CStr::from_ptr(config); //unsafe
if let Ok(config_field) = c.to_str() {
return log_field_array(js, ctx, config_field, email_field).is_ok();
}
}
return false;
}

fn log_field_comma(
js: &mut JsonBuilder, ctx: &mut MimeStateSMTP, c: &str, e: &str,
) -> Result<(), JsonError> {
for h in &ctx.headers[..ctx.main_headers_nb] {
if mime::slice_equals_lowercase(&h.name, e.as_bytes()) {
js.open_array(c)?;
for s in h.value.split(|c| *c == b',') {
js.append_string(&String::from_utf8_lossy(s))?;
}
js.close()?;
break;
}
}
return Ok(());
}

#[no_mangle]
pub unsafe extern "C" fn SCMimeSmtpLogFieldComma(
js: &mut JsonBuilder, ctx: &mut MimeStateSMTP, email: *const std::os::raw::c_char,
config: *const std::os::raw::c_char,
) -> bool {
let e: &CStr = CStr::from_ptr(email); //unsafe
if let Ok(email_field) = e.to_str() {
let c: &CStr = CStr::from_ptr(config); //unsafe
if let Ok(config_field) = c.to_str() {
return log_field_comma(js, ctx, config_field, email_field).is_ok();
}
}
return false;
}

fn log_field_string(
js: &mut JsonBuilder, ctx: &mut MimeStateSMTP, c: &str, e: &str,
) -> Result<(), JsonError> {
for h in &ctx.headers[..ctx.main_headers_nb] {
if mime::slice_equals_lowercase(&h.name, e.as_bytes()) {
js.set_string(c, &String::from_utf8_lossy(&h.value))?;
break;
}
}
return Ok(());
}

#[no_mangle]
pub unsafe extern "C" fn SCMimeSmtpLogFieldString(
js: &mut JsonBuilder, ctx: &mut MimeStateSMTP, email: *const std::os::raw::c_char,
config: *const std::os::raw::c_char,
) -> bool {
let e: &CStr = CStr::from_ptr(email); //unsafe
if let Ok(email_field) = e.to_str() {
let c: &CStr = CStr::from_ptr(config); //unsafe
if let Ok(config_field) = c.to_str() {
return log_field_string(js, ctx, config_field, email_field).is_ok();
}
}
return false;
}

fn log_data_header(
js: &mut JsonBuilder, ctx: &mut MimeStateSMTP, hname: &str,
) -> Result<(), JsonError> {
for h in &ctx.headers[..ctx.main_headers_nb] {
if mime::slice_equals_lowercase(&h.name, hname.as_bytes()) {
js.set_string(hname, &String::from_utf8_lossy(&h.value))?;
break;
}
}
return Ok(());
}

fn log_data(js: &mut JsonBuilder, ctx: &mut MimeStateSMTP) -> Result<(), JsonError> {
log_data_header(js, ctx, "from")?;
log_field_comma(js, ctx, "to", "to")?;
log_field_comma(js, ctx, "cc", "cc")?;

js.set_string("status", "PARSE_DONE")?;

if !ctx.attachments.is_empty() {
js.open_array("attachment")?;
for a in &ctx.attachments {
js.append_string(&String::from_utf8_lossy(a))?;
}
js.close()?;
}
if !ctx.urls.is_empty() {
js.open_array("url")?;
for a in ctx.urls.iter().rev() {
js.append_string(&String::from_utf8_lossy(a))?;
}
js.close()?;
}

return Ok(());
}

#[no_mangle]
pub unsafe extern "C" fn SCMimeSmtpLogData(js: &mut JsonBuilder, ctx: &mut MimeStateSMTP) -> bool {
return log_data(js, ctx).is_ok();
}
2 changes: 0 additions & 2 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,6 @@ noinst_HEADERS = \
util-datalink.h \
util-debug-filters.h \
util-debug.h \
util-decode-mime.h \
util-detect.h \
util-device.h \
util-dpdk.h \
Expand Down Expand Up @@ -1129,7 +1128,6 @@ libsuricata_c_a_SOURCES = \
util-datalink.c \
util-debug.c \
util-debug-filters.c \
util-decode-mime.c \
util-detect.c \
util-device.c \
util-dpdk.c \
Expand Down
11 changes: 5 additions & 6 deletions src/app-layer-htp.c
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ static void HtpTxUserDataFree(HtpState *state, HtpTxUserData *htud)
HTPFree(htud->response_headers_raw, htud->response_headers_raw_len);
AppLayerDecoderEventsFreeEvents(&htud->tx_data.events);
if (htud->mime_state)
rs_mime_state_free(htud->mime_state);
SCMimeStateFree(htud->mime_state);
if (htud->tx_data.de_state != NULL) {
DetectEngineStateFree(htud->tx_data.de_state);
}
Expand Down Expand Up @@ -1113,7 +1113,7 @@ static int HtpRequestBodySetupMultipart(htp_tx_t *tx, HtpTxUserData *htud)
htp_header_t *h = (htp_header_t *)htp_table_get_c(tx->request_headers,
"Content-Type");
if (h != NULL && bstr_len(h->value) > 0) {
htud->mime_state = rs_mime_state_init(bstr_ptr(h->value), bstr_len(h->value));
htud->mime_state = SCMimeStateInit(bstr_ptr(h->value), bstr_len(h->value));
if (htud->mime_state) {
htud->tsflags |= HTP_BOUNDARY_SET;
SCReturnInt(1);
Expand Down Expand Up @@ -1188,7 +1188,7 @@ static int HtpRequestBodyHandleMultipart(HtpState *hstate, HtpTxUserData *htud,
// keep parsing mime and use callbacks when needed
while (cur_buf_len > 0) {
MimeParserResult r =
rs_mime_parse(htud->mime_state, cur_buf, cur_buf_len, &consumed, &warnings);
SCMimeParse(htud->mime_state, cur_buf, cur_buf_len, &consumed, &warnings);
DEBUG_VALIDATE_BUG_ON(consumed > cur_buf_len);
htud->request_body.body_parsed += consumed;
if (warnings) {
Expand All @@ -1207,7 +1207,7 @@ static int HtpRequestBodyHandleMultipart(HtpState *hstate, HtpTxUserData *htud,
goto end;
case MimeFileOpen:
// get filename owned by mime state
rs_mime_state_get_filename(htud->mime_state, &filename, &filename_len);
SCMimeStateGetFilename(htud->mime_state, &filename, &filename_len);
if (filename_len > 0) {
htud->tsflags |= HTP_FILENAME_SET;
htud->tsflags &= ~HTP_DONTSTORE;
Expand Down Expand Up @@ -1245,7 +1245,6 @@ static int HtpRequestBodyHandleMultipart(HtpState *hstate, HtpTxUserData *htud,
}
htud->tsflags &= ~HTP_FILENAME_SET;
break;
// TODO event on parsing error ?
}
cur_buf += consumed;
cur_buf_len -= consumed;
Expand Down Expand Up @@ -5655,7 +5654,7 @@ static int HTPBodyReassemblyTest01(void)
printf("REASSCHUNK END: \n");
#endif

htud.mime_state = rs_mime_state_init((const uint8_t *)"multipart/form-data; boundary=toto",
htud.mime_state = SCMimeStateInit((const uint8_t *)"multipart/form-data; boundary=toto",
strlen("multipart/form-data; boundary=toto"));
FAIL_IF_NULL(htud.mime_state);
htud.tsflags |= HTP_BOUNDARY_SET;
Expand Down
Loading

0 comments on commit 32bf3b4

Please sign in to comment.