aboutsummaryrefslogblamecommitdiff
path: root/aero-sasl/src/decode.rs
blob: f5d7b538849d7ae17a5c8d37cb72678caba05ad0 (plain) (tree)


















































































































































































































































                                                                                                    
use base64::Engine;
use nom::{
    branch::alt,
    bytes::complete::{tag, tag_no_case, take, take_while, take_while1},
    character::complete::{tab, u16, u64},
    combinator::{map, opt, recognize, rest, value},
    error::{Error, ErrorKind},
    multi::{many1, separated_list0},
    sequence::{pair, preceded, tuple},
    IResult,
};

use super::types::*;

pub fn client_command<'a>(input: &'a [u8]) -> IResult<&'a [u8], ClientCommand> {
    alt((version_command, cpid_command, auth_command, cont_command))(input)
}

/*
fn server_command(buf: &u8) -> IResult<&u8, ServerCommand> {
    unimplemented!();
}
*/

// ---------------------

fn version_command<'a>(input: &'a [u8]) -> IResult<&'a [u8], ClientCommand> {
    let mut parser = tuple((tag_no_case(b"VERSION"), tab, u64, tab, u64));

    let (input, (_, _, major, _, minor)) = parser(input)?;
    Ok((input, ClientCommand::Version(Version { major, minor })))
}

pub fn cpid_command<'a>(input: &'a [u8]) -> IResult<&'a [u8], ClientCommand> {
    preceded(
        pair(tag_no_case(b"CPID"), tab),
        map(u64, |v| ClientCommand::Cpid(v)),
    )(input)
}

fn mechanism<'a>(input: &'a [u8]) -> IResult<&'a [u8], Mechanism> {
    alt((
        value(Mechanism::Plain, tag_no_case(b"PLAIN")),
        value(Mechanism::Login, tag_no_case(b"LOGIN")),
    ))(input)
}

fn is_not_tab_or_esc_or_lf(c: u8) -> bool {
    c != 0x09 && c != 0x01 && c != 0x0a // TAB or 0x01 or LF
}

fn is_esc<'a>(input: &'a [u8]) -> IResult<&'a [u8], &[u8]> {
    preceded(tag(&[0x01]), take(1usize))(input)
}

fn parameter<'a>(input: &'a [u8]) -> IResult<&'a [u8], &[u8]> {
    recognize(many1(alt((take_while1(is_not_tab_or_esc_or_lf), is_esc))))(input)
}

fn parameter_str(input: &[u8]) -> IResult<&[u8], String> {
    let (input, buf) = parameter(input)?;

    std::str::from_utf8(buf)
        .map(|v| (input, v.to_string()))
        .map_err(|_| nom::Err::Failure(Error::new(input, ErrorKind::TakeWhile1)))
}

fn is_param_name_char(c: u8) -> bool {
    is_not_tab_or_esc_or_lf(c) && c != 0x3d // =
}

fn parameter_name(input: &[u8]) -> IResult<&[u8], String> {
    let (input, buf) = take_while1(is_param_name_char)(input)?;

    std::str::from_utf8(buf)
        .map(|v| (input, v.to_string()))
        .map_err(|_| nom::Err::Failure(Error::new(input, ErrorKind::TakeWhile1)))
}

fn service<'a>(input: &'a [u8]) -> IResult<&'a [u8], String> {
    preceded(tag_no_case("service="), parameter_str)(input)
}

fn auth_option<'a>(input: &'a [u8]) -> IResult<&'a [u8], AuthOption> {
    use AuthOption::*;
    alt((
        alt((
            value(Debug, tag_no_case(b"debug")),
            value(NoPenalty, tag_no_case(b"no-penalty")),
            value(ClientId, tag_no_case(b"client_id")),
            value(NoLogin, tag_no_case(b"nologin")),
            map(preceded(tag_no_case(b"session="), u64), |id| Session(id)),
            map(preceded(tag_no_case(b"lip="), parameter_str), |ip| {
                LocalIp(ip)
            }),
            map(preceded(tag_no_case(b"rip="), parameter_str), |ip| {
                RemoteIp(ip)
            }),
            map(preceded(tag_no_case(b"lport="), u16), |port| {
                LocalPort(port)
            }),
            map(preceded(tag_no_case(b"rport="), u16), |port| {
                RemotePort(port)
            }),
            map(preceded(tag_no_case(b"real_rip="), parameter_str), |ip| {
                RealRemoteIp(ip)
            }),
            map(preceded(tag_no_case(b"real_lip="), parameter_str), |ip| {
                RealLocalIp(ip)
            }),
            map(preceded(tag_no_case(b"real_lport="), u16), |port| {
                RealLocalPort(port)
            }),
            map(preceded(tag_no_case(b"real_rport="), u16), |port| {
                RealRemotePort(port)
            }),
        )),
        alt((
            map(
                preceded(tag_no_case(b"local_name="), parameter_str),
                |name| LocalName(name),
            ),
            map(
                preceded(tag_no_case(b"forward_views="), parameter),
                |views| ForwardViews(views.into()),
            ),
            map(preceded(tag_no_case(b"secured="), parameter_str), |info| {
                Secured(Some(info))
            }),
            value(Secured(None), tag_no_case(b"secured")),
            value(CertUsername, tag_no_case(b"cert_username")),
            map(preceded(tag_no_case(b"transport="), parameter_str), |ts| {
                Transport(ts)
            }),
            map(
                preceded(tag_no_case(b"tls_cipher="), parameter_str),
                |cipher| TlsCipher(cipher),
            ),
            map(
                preceded(tag_no_case(b"tls_cipher_bits="), parameter_str),
                |bits| TlsCipherBits(bits),
            ),
            map(preceded(tag_no_case(b"tls_pfs="), parameter_str), |pfs| {
                TlsPfs(pfs)
            }),
            map(
                preceded(tag_no_case(b"tls_protocol="), parameter_str),
                |proto| TlsProtocol(proto),
            ),
            map(
                preceded(tag_no_case(b"valid-client-cert="), parameter_str),
                |cert| ValidClientCert(cert),
            ),
        )),
        alt((
            map(preceded(tag_no_case(b"resp="), base64), |data| Resp(data)),
            map(
                tuple((parameter_name, tag(b"="), parameter)),
                |(n, _, v)| UnknownPair(n, v.into()),
            ),
            map(parameter, |v| UnknownBool(v.into())),
        )),
    ))(input)
}

fn auth_command<'a>(input: &'a [u8]) -> IResult<&'a [u8], ClientCommand> {
    let mut parser = tuple((
        tag_no_case(b"AUTH"),
        tab,
        u64,
        tab,
        mechanism,
        tab,
        service,
        map(opt(preceded(tab, separated_list0(tab, auth_option))), |o| {
            o.unwrap_or(vec![])
        }),
    ));
    let (input, (_, _, id, _, mech, _, service, options)) = parser(input)?;
    Ok((
        input,
        ClientCommand::Auth {
            id,
            mech,
            service,
            options,
        },
    ))
}

fn is_base64_core(c: u8) -> bool {
    c >= 0x30 && c <= 0x39 // 0-9 
        || c >= 0x41 && c <= 0x5a // A-Z
        || c >= 0x61 && c <= 0x7a // a-z
        || c == 0x2b // +
        || c == 0x2f // /
}

fn is_base64_pad(c: u8) -> bool {
    c == 0x3d // =
}

fn base64(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
    let (input, (b64, _)) = tuple((take_while1(is_base64_core), take_while(is_base64_pad)))(input)?;

    let data = base64::engine::general_purpose::STANDARD_NO_PAD
        .decode(b64)
        .map_err(|_| nom::Err::Failure(Error::new(input, ErrorKind::TakeWhile1)))?;

    Ok((input, data))
}

/// @FIXME Dovecot does not say if base64 content must be padded or not
fn cont_command<'a>(input: &'a [u8]) -> IResult<&'a [u8], ClientCommand> {
    let mut parser = tuple((tag_no_case(b"CONT"), tab, u64, tab, base64));

    let (input, (_, _, id, _, data)) = parser(input)?;
    Ok((input, ClientCommand::Cont { id, data }))
}

// -----------------------------------------------------------------
//
// SASL DECODING
//
// -----------------------------------------------------------------

fn not_null(c: u8) -> bool {
    c != 0x0
}

// impersonated user, login, password
pub fn auth_plain<'a>(input: &'a [u8]) -> IResult<&'a [u8], (&'a [u8], &'a [u8], &'a [u8])> {
    map(
        tuple((
            take_while(not_null),
            take(1usize),
            take_while(not_null),
            take(1usize),
            rest,
        )),
        |(imp, _, user, _, pass)| (imp, user, pass),
    )(input)
}