diff options
Diffstat (limited to 'aero-sasl/src/decode.rs')
-rw-r--r-- | aero-sasl/src/decode.rs | 243 |
1 files changed, 243 insertions, 0 deletions
diff --git a/aero-sasl/src/decode.rs b/aero-sasl/src/decode.rs new file mode 100644 index 0000000..f5d7b53 --- /dev/null +++ b/aero-sasl/src/decode.rs @@ -0,0 +1,243 @@ +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) +} |