/* Copyright (C) 2020-2021 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.
 */

// Author: Frank Honza <frank.honza@dcso.de>

extern crate ipsec_parser;
use self::ipsec_parser::*;

use crate::applayer;
use crate::applayer::*;
use crate::core::{self, *};
use crate::direction::Direction;
use crate::flow::Flow;
use crate::ike::ikev1::{handle_ikev1, IkeV1Header, Ikev1Container};
use crate::ike::ikev2::{handle_ikev2, Ikev2Container};
use crate::ike::parser::*;
use nom7::Err;
use std;
use std::collections::HashSet;
use std::ffi::CString;
use suricata_sys::sys::{
    AppLayerParserState, AppProto, SCAppLayerParserConfParserEnabled,
    SCAppLayerProtoDetectConfProtoDetectionEnabled,
};

#[derive(AppLayerEvent)]
pub enum IkeEvent {
    MalformedData,
    NoEncryption,
    WeakCryptoEnc,
    WeakCryptoPrf,
    WeakCryptoDh,
    WeakCryptoAuth,
    WeakCryptoNoDh,
    WeakCryptoNoAuth,
    InvalidProposal,
    UnknownProposal,
    PayloadExtraData,
    MultipleServerProposal,
}

pub struct IkeHeaderWrapper {
    pub spi_initiator: String,
    pub spi_responder: String,
    pub maj_ver: u8,
    pub min_ver: u8,
    pub msg_id: u32,
    pub flags: u8,
    pub ikev1_transforms: Vec<Vec<SaAttribute>>,
    pub ikev2_transforms: Vec<IkeV2Transform>,
    pub ikev1_header: IkeV1Header,
    pub ikev2_header: IkeV2Header,
}

impl Default for IkeHeaderWrapper {
    fn default() -> Self {
        Self::new()
    }
}

impl IkeHeaderWrapper {
    pub fn new() -> Self {
        Self {
            spi_initiator: String::new(),
            spi_responder: String::new(),
            maj_ver: 0,
            min_ver: 0,
            msg_id: 0,
            flags: 0,
            ikev1_transforms: Vec::new(),
            ikev2_transforms: Vec::new(),
            ikev1_header: IkeV1Header::default(),
            ikev2_header: IkeV2Header {
                init_spi: 0,
                resp_spi: 0,
                next_payload: IkePayloadType::NoNextPayload,
                maj_ver: 0,
                min_ver: 0,
                exch_type: IkeExchangeType(0),
                flags: 0,
                msg_id: 0,
                length: 0,
            },
        }
    }
}

#[derive(Default)]
pub struct IkePayloadWrapper {
    pub ikev1_payload_types: Option<HashSet<u8>>,
    pub ikev2_payload_types: Vec<IkePayloadType>,
}

#[derive(Default)]
pub struct IKETransaction {
    tx_id: u64,

    pub ike_version: u8,
    pub direction: Direction,
    pub hdr: IkeHeaderWrapper,
    pub payload_types: IkePayloadWrapper,
    pub notify_types: Vec<NotifyType>,

    /// errors seen during exchange
    pub errors: u32,

    pub tx_data: applayer::AppLayerTxData,
}

impl Transaction for IKETransaction {
    fn id(&self) -> u64 {
        self.tx_id
    }
}

impl IKETransaction {
    pub fn new(direction: Direction) -> Self {
        Self {
            direction,
            tx_data: applayer::AppLayerTxData::for_direction(direction),
            ..Default::default()
        }
    }

    /// Set an event.
    pub fn set_event(&mut self, event: IkeEvent) {
        self.tx_data.set_event(event as u8);
    }
}

#[derive(Default)]
pub struct IKEState {
    state_data: AppLayerStateData,
    tx_id: u64,
    pub transactions: Vec<IKETransaction>,

    pub ikev1_container: Ikev1Container,
    pub ikev2_container: Ikev2Container,
}

impl State<IKETransaction> for IKEState {
    fn get_transaction_count(&self) -> usize {
        self.transactions.len()
    }

    fn get_transaction_by_index(&self, index: usize) -> Option<&IKETransaction> {
        self.transactions.get(index)
    }
}

impl IKEState {
    // Free a transaction by ID.
    fn free_tx(&mut self, tx_id: u64) {
        let tx = self
            .transactions
            .iter()
            .position(|tx| tx.tx_id == tx_id + 1);
        debug_assert!(tx.is_some());
        if let Some(idx) = tx {
            let _ = self.transactions.remove(idx);
        }
    }

    pub fn get_tx(&mut self, tx_id: u64) -> Option<&mut IKETransaction> {
        self.transactions
            .iter_mut()
            .find(|tx| tx.tx_id == tx_id + 1)
    }

    pub fn new_tx(&mut self, direction: Direction) -> IKETransaction {
        let mut tx = IKETransaction::new(direction);
        self.tx_id += 1;
        tx.tx_id = self.tx_id;
        return tx;
    }

    /// Set an event. The event is set on the most recent transaction.
    pub fn set_event(&mut self, event: IkeEvent) {
        if let Some(tx) = self.transactions.last_mut() {
            tx.set_event(event);
        } else {
            SCLogDebug!(
                "IKE: trying to set event {} on non-existing transaction",
                event as u32
            );
        }
    }

    fn handle_input(&mut self, input: &[u8], direction: Direction) -> AppLayerResult {
        // We're not interested in empty requests.
        if input.is_empty() {
            return AppLayerResult::ok();
        }

        let mut current = input;
        match parse_isakmp_header(current) {
            Ok((rem, isakmp_header)) => {
                current = rem;

                if isakmp_header.maj_ver != 1 && isakmp_header.maj_ver != 2 {
                    SCLogDebug!("Unsupported ISAKMP major_version");
                    return AppLayerResult::err();
                }

                if isakmp_header.maj_ver == 1 {
                    handle_ikev1(self, current, isakmp_header, direction);
                } else if isakmp_header.maj_ver == 2 {
                    handle_ikev2(self, current, isakmp_header, direction);
                } else {
                    return AppLayerResult::err();
                }
                return AppLayerResult::ok(); // todo either remove outer loop or check header length-field if we have completely read everything
            }
            Err(Err::Incomplete(_)) => {
                SCLogDebug!("Insufficient data while parsing IKE");
                return AppLayerResult::err();
            }
            Err(_) => {
                SCLogDebug!("Error while parsing IKE packet");
                return AppLayerResult::err();
            }
        }
    }
}

/// Probe to see if this input looks like a request or response.
fn probe(input: &[u8], direction: Direction, rdir: *mut u8) -> bool {
    match parse_isakmp_header(input) {
        Ok((_, isakmp_header)) => {
            if isakmp_header.maj_ver == 1 {
                if isakmp_header.resp_spi == 0 && direction != Direction::ToServer {
                    unsafe {
                        *rdir = Direction::ToServer.into();
                    }
                }
                return true;
            } else if isakmp_header.maj_ver == 2 {
                if isakmp_header.min_ver != 0 {
                    SCLogDebug!(
                        "ipsec_probe: could be ipsec, but with unsupported/invalid version {}.{}",
                        isakmp_header.maj_ver,
                        isakmp_header.min_ver
                    );
                    return false;
                }
                if isakmp_header.exch_type < 34 || isakmp_header.exch_type > 37 {
                    SCLogDebug!("ipsec_probe: could be ipsec, but with unsupported/invalid exchange type {}",
                           isakmp_header.exch_type);
                    return false;
                }
                if isakmp_header.length as usize != input.len() {
                    SCLogDebug!("ipsec_probe: could be ipsec, but length does not match");
                    return false;
                }

                if isakmp_header.resp_spi == 0 && direction != Direction::ToServer {
                    unsafe {
                        *rdir = Direction::ToServer.into();
                    }
                }
                return true;
            }

            return false;
        }
        Err(_) => return false,
    }
}

// C exports.

/// C entry point for a probing parser.
unsafe extern "C" fn ike_probing_parser(
    _flow: *const Flow, direction: u8, input: *const u8, input_len: u32, rdir: *mut u8,
) -> AppProto {
    if input_len < 28 {
        // at least the ISAKMP_HEADER must be there, not ALPROTO_UNKNOWN because over UDP
        return ALPROTO_FAILED;
    }

    if !input.is_null() {
        let slice = build_slice!(input, input_len as usize);
        if probe(slice, direction.into(), rdir) {
            return ALPROTO_IKE;
        }
    }
    return ALPROTO_FAILED;
}

extern "C" fn ike_state_new(
    _orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto,
) -> *mut std::os::raw::c_void {
    let state = IKEState::default();
    let boxed = Box::new(state);
    return Box::into_raw(boxed) as *mut _;
}

unsafe extern "C" fn ike_state_free(state: *mut std::os::raw::c_void) {
    // Just unbox...
    std::mem::drop(Box::from_raw(state as *mut IKEState));
}

unsafe extern "C" fn ike_state_tx_free(state: *mut std::os::raw::c_void, tx_id: u64) {
    let state = cast_pointer!(state, IKEState);
    state.free_tx(tx_id);
}

unsafe extern "C" fn ike_parse_request(
    _flow: *mut Flow, state: *mut std::os::raw::c_void, _pstate: *mut AppLayerParserState,
    stream_slice: StreamSlice, _data: *const std::os::raw::c_void,
) -> AppLayerResult {
    let state = cast_pointer!(state, IKEState);
    return state.handle_input(stream_slice.as_slice(), Direction::ToServer);
}

unsafe extern "C" fn ike_parse_response(
    _flow: *mut Flow, state: *mut std::os::raw::c_void, _pstate: *mut AppLayerParserState,
    stream_slice: StreamSlice, _data: *const std::os::raw::c_void,
) -> AppLayerResult {
    let state = cast_pointer!(state, IKEState);
    return state.handle_input(stream_slice.as_slice(), Direction::ToClient);
}

unsafe extern "C" fn ike_state_get_tx(
    state: *mut std::os::raw::c_void, tx_id: u64,
) -> *mut std::os::raw::c_void {
    let state = cast_pointer!(state, IKEState);
    match state.get_tx(tx_id) {
        Some(tx) => {
            return tx as *const _ as *mut _;
        }
        None => {
            return std::ptr::null_mut();
        }
    }
}

unsafe extern "C" fn ike_state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 {
    let state = cast_pointer!(state, IKEState);
    return state.tx_id;
}

extern "C" fn ike_tx_get_alstate_progress(
    _tx: *mut std::os::raw::c_void, _direction: u8,
) -> std::os::raw::c_int {
    return 1;
}

static mut ALPROTO_IKE: AppProto = ALPROTO_UNKNOWN;

// Parser name as a C style string.
const PARSER_NAME: &[u8] = b"ike\0";
const PARSER_ALIAS: &[u8] = b"ikev2\0";

export_tx_data_get!(ike_get_tx_data, IKETransaction);
export_state_data_get!(ike_get_state_data, IKEState);

#[no_mangle]
pub unsafe extern "C" fn SCRegisterIkeParser() {
    let default_port = CString::new("500").unwrap();
    let parser = RustParser {
        name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char,
        default_port: default_port.as_ptr(),
        ipproto: core::IPPROTO_UDP,
        probe_ts: Some(ike_probing_parser),
        probe_tc: Some(ike_probing_parser),
        min_depth: 0,
        max_depth: 16,
        state_new: ike_state_new,
        state_free: ike_state_free,
        tx_free: ike_state_tx_free,
        parse_ts: ike_parse_request,
        parse_tc: ike_parse_response,
        get_tx_count: ike_state_get_tx_count,
        get_tx: ike_state_get_tx,
        tx_comp_st_ts: 1,
        tx_comp_st_tc: 1,
        tx_get_progress: ike_tx_get_alstate_progress,
        get_eventinfo: Some(IkeEvent::get_event_info),
        get_eventinfo_byid: Some(IkeEvent::get_event_info_by_id),
        localstorage_new: None,
        localstorage_free: None,
        get_tx_files: None,
        get_tx_iterator: Some(applayer::state_get_tx_iterator::<IKEState, IKETransaction>),
        get_tx_data: ike_get_tx_data,
        get_state_data: ike_get_state_data,
        apply_tx_config: None,
        flags: 0,
        get_frame_id_by_name: None,
        get_frame_name_by_id: None,
        get_state_id_by_name: None,
        get_state_name_by_id: None,
    };

    let ip_proto_str = CString::new("udp").unwrap();

    if SCAppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
        let alproto = AppLayerRegisterProtocolDetection(&parser, 1);
        ALPROTO_IKE = alproto;
        if SCAppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
            let _ = AppLayerRegisterParser(&parser, alproto);
        }

        AppLayerRegisterParserAlias(
            PARSER_NAME.as_ptr() as *const std::os::raw::c_char,
            PARSER_ALIAS.as_ptr() as *const std::os::raw::c_char,
        );
        SCLogDebug!("Rust IKE parser registered.");
    } else {
        SCLogDebug!("Protocol detector and parser disabled for IKE.");
    }
}
