/* Copyright (C) 2024 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 Giuseppe Longo use crate::detect::EnumString; use crate::jsonbuilder::{JsonBuilder, JsonError}; use crate::ldap::ldap::LdapTransaction; use crate::ldap::types::*; use ldap_parser::filter::*; use ldap_parser::ldap::*; fn log_ldap(tx: &LdapTransaction, js: &mut JsonBuilder) -> Result<(), JsonError> { js.open_object("ldap")?; if let Some(req) = &tx.request { let protocol_op_str = ldap_protocol_op_as_str(&req.protocol_op); js.open_object("request")?; js.set_uint("message_id", req.message_id.0)?; js.set_string("operation", protocol_op_str)?; match &req.protocol_op { ProtocolOp::SearchRequest(msg) => log_search_request(msg, js)?, ProtocolOp::BindRequest(msg) => log_bind_request(msg, js)?, ProtocolOp::UnbindRequest => (), ProtocolOp::ModifyRequest(msg) => log_modify_request(msg, js)?, ProtocolOp::AddRequest(msg) => log_add_request(msg, js)?, ProtocolOp::DelRequest(msg) => log_del_request(msg, js)?, ProtocolOp::ModDnRequest(msg) => log_mod_dn_request(msg, js)?, ProtocolOp::CompareRequest(msg) => log_compare_request(msg, js)?, ProtocolOp::ExtendedRequest(msg) => log_extended_request(msg, js)?, _ => {} }; log_controls(&req.controls, js)?; js.close()?; } if !tx.responses.is_empty() { js.open_array("responses")?; for response in &tx.responses { js.start_object()?; let protocol_op_str = ldap_protocol_op_as_str(&response.protocol_op); js.set_string("operation", protocol_op_str)?; if tx.request.is_none() { js.set_uint("message_id", response.message_id.0)?; } match &response.protocol_op { ProtocolOp::SearchResultEntry(msg) => log_search_result_entry(msg, js)?, ProtocolOp::SearchResultDone(msg) => log_search_result_done(msg, js)?, ProtocolOp::BindResponse(msg) => log_bind_response(msg, js)?, ProtocolOp::ModifyResponse(msg) => log_modify_response(msg, js)?, ProtocolOp::AddResponse(msg) => log_add_response(msg, js)?, ProtocolOp::DelResponse(msg) => log_del_response(msg, js)?, ProtocolOp::ModDnResponse(msg) => log_mod_dn_response(msg, js)?, ProtocolOp::CompareResponse(msg) => log_compare_response(msg, js)?, ProtocolOp::ExtendedResponse(msg) => log_extended_response(msg, js)?, ProtocolOp::IntermediateResponse(msg) => log_intermediate_response(msg, js)?, _ => {} } log_controls(&response.controls, js)?; js.close()?; } js.close()?; } js.close()?; Ok(()) } fn log_search_request(msg: &SearchRequest, js: &mut JsonBuilder) -> Result<(), JsonError> { js.open_object("search_request")?; js.set_string("base_object", &msg.base_object.0)?; js.set_uint("scope", msg.scope.0)?; js.set_uint("deref_alias", msg.deref_aliases.0)?; js.set_uint("size_limit", msg.size_limit)?; js.set_uint("time_limit", msg.time_limit)?; js.set_bool("types_only", msg.types_only)?; if let Filter::Present(val) = &msg.filter { js.open_object("filter")?; js.set_string("type", "present")?; js.set_string("value", &val.0)?; js.close()?; } if !msg.attributes.is_empty() { js.open_array("attributes")?; for attr in &msg.attributes { js.append_string(&attr.0)?; } js.close()?; } js.close()?; Ok(()) } fn log_bind_request(msg: &BindRequest, js: &mut JsonBuilder) -> Result<(), JsonError> { js.open_object("bind_request")?; js.set_uint("version", msg.version)?; js.set_string("name", &msg.name.0)?; if let AuthenticationChoice::Sasl(sasl) = &msg.authentication { js.open_object("sasl")?; js.set_string("mechanism", &sasl.mechanism.0)?; if let Some(credentials) = &sasl.credentials { js.set_hex("credentials", credentials)?; } js.close()?; } js.close()?; Ok(()) } fn log_modify_request(msg: &ModifyRequest, js: &mut JsonBuilder) -> Result<(), JsonError> { js.open_object("modify_request")?; js.set_string("object", &msg.object.0)?; if !msg.changes.is_empty() { js.open_array("changes")?; for change in &msg.changes { js.start_object()?; js.set_string( "operation", ldap_operation_to_string(&change.operation).as_str(), )?; js.open_object("modification")?; js.set_string("attribute_type", &change.modification.attr_type.0)?; if !change.modification.attr_vals.is_empty() { js.open_array("attribute_values")?; for attr in &change.modification.attr_vals { js.append_string_from_bytes(&attr.0[..])?; } js.close()?; } js.close()?; js.close()?; } js.close()?; } js.close()?; Ok(()) } fn log_add_request(msg: &AddRequest, js: &mut JsonBuilder) -> Result<(), JsonError> { js.open_object("add_request")?; js.set_string("entry", &msg.entry.0)?; if !msg.attributes.is_empty() { js.open_array("attributes")?; for attr in &msg.attributes { js.start_object()?; js.set_string("name", &attr.attr_type.0)?; if !attr.attr_vals.is_empty() { js.open_array("values")?; for val in &attr.attr_vals { js.append_string_from_bytes(&val.0)?; } js.close()?; } js.close()?; } js.close()?; } js.close()?; Ok(()) } fn log_del_request(msg: &LdapDN, js: &mut JsonBuilder) -> Result<(), JsonError> { js.open_object("del_request")?; js.set_string("dn", &msg.0)?; js.close()?; Ok(()) } fn log_mod_dn_request(msg: &ModDnRequest, js: &mut JsonBuilder) -> Result<(), JsonError> { js.open_object("mod_dn_request")?; js.set_string("entry", &msg.entry.0)?; js.set_string("new_rdn", &msg.newrdn.0)?; js.set_bool("delete_old_rdn", msg.deleteoldrdn)?; if let Some(newsuperior) = &msg.newsuperior { js.set_string("new_superior", &newsuperior.0)?; } js.close()?; Ok(()) } fn log_compare_request(msg: &CompareRequest, js: &mut JsonBuilder) -> Result<(), JsonError> { js.open_object("compare_request")?; js.set_string("entry", &msg.entry.0)?; js.open_object("attribute_value_assertion")?; js.set_string("description", &msg.ava.attribute_desc.0)?; js.set_string_from_bytes("value", &msg.ava.assertion_value[..])?; js.close()?; js.close()?; Ok(()) } fn log_extended_request(msg: &ExtendedRequest, js: &mut JsonBuilder) -> Result<(), JsonError> { js.open_object("extended_request")?; js.set_string("name", &msg.request_name.0)?; if let Some(value) = &msg.request_value { js.set_string_from_bytes("value", &value[..])?; } js.close()?; Ok(()) } fn log_search_result_entry(msg: &SearchResultEntry, js: &mut JsonBuilder) -> Result<(), JsonError> { js.open_object("search_result_entry")?; js.set_string("base_object", &msg.object_name.0)?; if !msg.attributes.is_empty() { js.open_array("attributes")?; for attr in &msg.attributes { js.start_object()?; js.set_string("type", &attr.attr_type.0)?; if !attr.attr_vals.is_empty() { js.open_array("values")?; for val in &attr.attr_vals { js.append_string_from_bytes(&val.0)?; } js.close()?; } js.close()?; } js.close()?; } js.close()?; Ok(()) } fn log_search_result_done(msg: &LdapResult, js: &mut JsonBuilder) -> Result<(), JsonError> { js.open_object("search_result_done")?; log_ldap_result(msg, js)?; js.close()?; Ok(()) } fn log_bind_response(msg: &BindResponse, js: &mut JsonBuilder) -> Result<(), JsonError> { js.open_object("bind_response")?; log_ldap_result(&msg.result, js)?; if let Some(creds) = &msg.server_sasl_creds { js.set_hex("server_sasl_creds", creds)?; }; js.close()?; Ok(()) } fn log_modify_response(msg: &ModifyResponse, js: &mut JsonBuilder) -> Result<(), JsonError> { js.open_object("modify_response")?; log_ldap_result(&msg.result, js)?; js.close()?; Ok(()) } fn log_add_response(msg: &LdapResult, js: &mut JsonBuilder) -> Result<(), JsonError> { js.open_object("add_response")?; log_ldap_result(msg, js)?; js.close()?; Ok(()) } fn log_del_response(msg: &LdapResult, js: &mut JsonBuilder) -> Result<(), JsonError> { js.open_object("del_response")?; log_ldap_result(msg, js)?; js.close()?; Ok(()) } fn log_mod_dn_response(msg: &LdapResult, js: &mut JsonBuilder) -> Result<(), JsonError> { js.open_object("mod_dn_response")?; log_ldap_result(msg, js)?; js.close()?; Ok(()) } fn log_compare_response(msg: &LdapResult, js: &mut JsonBuilder) -> Result<(), JsonError> { js.open_object("compare_response")?; log_ldap_result(msg, js)?; js.close()?; Ok(()) } fn log_extended_response(msg: &ExtendedResponse, js: &mut JsonBuilder) -> Result<(), JsonError> { js.open_object("extended_response")?; log_ldap_result(&msg.result, js)?; if let Some(name) = &msg.response_name { js.set_string("name", &name.0)?; } if let Some(value) = &msg.response_value { js.set_string_from_bytes("value", &value[..])?; } js.close()?; Ok(()) } fn log_intermediate_response( msg: &IntermediateResponse, js: &mut JsonBuilder, ) -> Result<(), JsonError> { js.open_object("intermediate_response")?; if let Some(name) = &msg.response_name { js.set_string("name", &name.0)?; } if let Some(value) = &msg.response_value { js.set_string_from_bytes("value", &value[..])?; } js.close()?; Ok(()) } fn log_ldap_result(msg: &LdapResult, js: &mut JsonBuilder) -> Result<(), JsonError> { if let Some(rc) = LdapResultCode::from_u(msg.result_code.0) { js.set_string("result_code", rc.to_str())?; } else { js.set_string("result_code", &format!("unknown-{}", msg.result_code.0))?; } js.set_string("matched_dn", &msg.matched_dn.0)?; js.set_string("message", &msg.diagnostic_message.0)?; Ok(()) } fn log_controls(controls: &Option>, js: &mut JsonBuilder) -> Result<(), JsonError> { if let Some(ctls) = controls { js.open_array("controls")?; for ctl in ctls { js.start_object()?; js.set_string("control_type", &ctl.control_type.0)?; js.set_bool("criticality", ctl.criticality)?; if let Some(ctl_val) = &ctl.control_value { js.set_hex("control_value", ctl_val)?; }; js.close()?; } js.close()?; } Ok(()) } #[no_mangle] pub unsafe extern "C" fn SCLdapLoggerLog( tx: *mut std::os::raw::c_void, js: &mut JsonBuilder, ) -> bool { let tx = cast_pointer!(tx, LdapTransaction); log_ldap(tx, js).is_ok() }