/* 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 std; use std::os::raw::c_void; use crate::dhcp::dhcp::*; use crate::dhcp::parser::{DHCPOptionWrapper,DHCPOptGeneric}; use crate::dns::log::dns_print_addr; use crate::conf::ConfNode; use crate::jsonbuilder::{JsonBuilder, JsonError}; pub struct DHCPLogger { extended: bool, } impl DHCPLogger { pub fn new(conf: ConfNode) -> Self { return Self { extended: conf.get_child_bool("extended"), } } fn get_type(&self, tx: &DHCPTransaction) -> Option { let options = &tx.message.options; for option in options { let code = option.code; #[allow(clippy::single_match)] match &option.option { DHCPOptionWrapper::Generic(option) => { #[allow(clippy::single_match)] match code { DHCP_OPT_TYPE => { if !option.data.is_empty() { return Some(option.data[0]); } } _ => {} } } _ => {} } } return None; } pub fn do_log(&self, tx: &DHCPTransaction) -> bool { if !self.extended { if let Some(DHCP_TYPE_ACK) = self.get_type(tx){ return true; } return false; } return true; } pub fn log(&self, tx: &DHCPTransaction, js: &mut JsonBuilder) -> Result<(), JsonError> { let header = &tx.message.header; let options = &tx.message.options; js.open_object("dhcp")?; match header.opcode { BOOTP_REQUEST => { js.set_string("type", "request")?; } BOOTP_REPLY => { js.set_string("type", "reply")?; } _ => { js.set_string("type", "")?; } } js.set_uint("id", header.txid as u64)?; js.set_string("client_mac", &format_addr_hex(&header.clienthw.to_vec()))?; js.set_string("assigned_ip", &dns_print_addr(&header.yourip))?; if self.extended { js.set_string("client_ip", &dns_print_addr(&header.clientip))?; if header.opcode == BOOTP_REPLY { js.set_string("relay_ip", &dns_print_addr(&header.giaddr))?; js.set_string("next_server_ip", &dns_print_addr(&header.serverip))?; } } for option in options { let code = option.code; match option.option { DHCPOptionWrapper::ClientId(ref clientid) => { js.set_string("client_id", &format_addr_hex(&clientid.data))?; } DHCPOptionWrapper::TimeValue(ref time_value) => { match code { DHCP_OPT_ADDRESS_TIME => { if self.extended { js.set_uint("lease_time", time_value.seconds as u64)?; } } DHCP_OPT_REBINDING_TIME => { if self.extended { js.set_uint("rebinding_time", time_value.seconds as u64)?; } } DHCP_OPT_RENEWAL_TIME => { js.set_uint("renewal_time", time_value.seconds as u64)?; } _ => {} } } DHCPOptionWrapper::Generic(ref option) => { match code { DHCP_OPT_SUBNET_MASK => { if self.extended { js.set_string("subnet_mask", &dns_print_addr(&option.data))?; } } DHCP_OPT_HOSTNAME => { if !option.data.is_empty() { js.set_string_from_bytes("hostname", &option.data)?; } } DHCP_OPT_TYPE => { self.log_opt_type(js, option)?; } DHCP_OPT_REQUESTED_IP => { if self.extended { js.set_string("requested_ip", &dns_print_addr(&option.data))?; } } DHCP_OPT_PARAMETER_LIST => { if self.extended { self.log_opt_parameters(js, option)?; } } DHCP_OPT_DNS_SERVER => { if self.extended { self.log_opt_dns_server(js, option)?; } } DHCP_OPT_ROUTERS => { if self.extended { self.log_opt_routers(js, option)?; } } DHCP_OPT_VENDOR_CLASS_ID => { if self.extended && !option.data.is_empty(){ js.set_string_from_bytes("vendor_class_identifier", &option.data)?; } } _ => {} } } _ => {} } } js.close()?; return Ok(()); } fn log_opt_type(&self, js: &mut JsonBuilder, option: &DHCPOptGeneric) -> Result<(), JsonError> { if !option.data.is_empty() { let dhcp_type = match option.data[0] { DHCP_TYPE_DISCOVER => "discover", DHCP_TYPE_OFFER => "offer", DHCP_TYPE_REQUEST => "request", DHCP_TYPE_DECLINE => "decline", DHCP_TYPE_ACK => "ack", DHCP_TYPE_NAK => "nak", DHCP_TYPE_RELEASE => "release", DHCP_TYPE_INFORM => "inform", _ => "unknown" }; js.set_string("dhcp_type", dhcp_type)?; } Ok(()) } fn log_opt_parameters(&self, js: &mut JsonBuilder, option: &DHCPOptGeneric) -> Result<(), JsonError> { js.open_array("params")?; for i in &option.data { let param = match *i { DHCP_PARAM_SUBNET_MASK => "subnet_mask", DHCP_PARAM_ROUTER => "router", DHCP_PARAM_DNS_SERVER => "dns_server", DHCP_PARAM_DOMAIN => "domain", DHCP_PARAM_ARP_TIMEOUT => "arp_timeout", DHCP_PARAM_NTP_SERVER => "ntp_server", DHCP_PARAM_TFTP_SERVER_NAME => "tftp_server_name", DHCP_PARAM_TFTP_SERVER_IP => "tftp_server_ip", _ => "" }; if !param.is_empty() { js.append_string(param)?; } } js.close()?; Ok(()) } fn log_opt_dns_server(&self, js: &mut JsonBuilder, option: &DHCPOptGeneric) -> Result<(), JsonError> { js.open_array("dns_servers")?; for i in 0..(option.data.len() / 4) { let val = dns_print_addr(&option.data[(i * 4)..(i * 4) + 4]); js.append_string(&val)?; } js.close()?; Ok(()) } fn log_opt_routers(&self, js: &mut JsonBuilder, option: &DHCPOptGeneric) -> Result<(), JsonError> { js.open_array("routers")?; for i in 0..(option.data.len() / 4) { let val = dns_print_addr(&option.data[(i * 4)..(i * 4) + 4]); js.append_string(&val)?; } js.close()?; Ok(()) } } fn format_addr_hex(input: &[u8]) -> String { let parts: Vec = input.iter() .map(|b| format!("{:02x}", b)) .collect(); return parts.join(":"); } #[no_mangle] pub extern "C" fn rs_dhcp_logger_new(conf: *const c_void) -> *mut std::os::raw::c_void { let conf = ConfNode::wrap(conf); let boxed = Box::new(DHCPLogger::new(conf)); return Box::into_raw(boxed) as *mut _; } #[no_mangle] pub unsafe extern "C" fn rs_dhcp_logger_free(logger: *mut std::os::raw::c_void) { std::mem::drop(Box::from_raw(logger as *mut DHCPLogger)); } #[no_mangle] pub unsafe extern "C" fn rs_dhcp_logger_log(logger: *mut std::os::raw::c_void, tx: *mut std::os::raw::c_void, js: &mut JsonBuilder) -> bool { let logger = cast_pointer!(logger, DHCPLogger); let tx = cast_pointer!(tx, DHCPTransaction); logger.log(tx, js).is_ok() } #[no_mangle] pub unsafe extern "C" fn rs_dhcp_logger_do_log(logger: *mut std::os::raw::c_void, tx: *mut std::os::raw::c_void) -> bool { let logger = cast_pointer!(logger, DHCPLogger); let tx = cast_pointer!(tx, DHCPTransaction); logger.do_log(tx) }