use std::str;
use nom::{Err,IResult,ErrorKind,crlf,not_line_ending,line_ending,be_u8,be_u32};
use nom::types::CompleteByteSlice;
use nom;
#[derive(Debug,PartialEq)]
pub struct SshVersion<'a> {
pub proto: &'a [u8],
pub software: &'a [u8],
pub comments: Option<&'a[u8]>,
}
named!(parse_version<SshVersion>, do_parse!(
proto: take_until_and_consume_s!("-") >>
software: is_not_s!(" \r\n") >>
comments: opt!(do_parse!(
tag!(" ") >>
comments: not_line_ending >>
( comments ))
) >>
( SshVersion { proto: proto, software: software, comments: comments } )
));
pub fn parse_ssh_identification(i: &[u8]) -> IResult<&[u8], (Vec<&[u8]>, SshVersion)> {
many_till!(i,
terminated!(take_until_s!("\r\n"), crlf),
delimited!(tag!("SSH-"), parse_version, line_ending)
)
}
named!(parse_string<&[u8]>, do_parse!(
len: be_u32 >>
string: take!(len) >>
( string )
));
#[inline]
fn is_us_ascii(c: u8) -> bool {
c >= 0x20 && c <= 0x7e && c != 0x2c
}
named!(parse_name<CompleteByteSlice, &[u8]>, map!(take_while!(is_us_ascii), |b| b.0));
fn parse_name_list<'a>(i: &'a[u8]) -> IResult<&'a[u8], Vec<&str>> {
match separated_list_complete!(CompleteByteSlice(i), tag!(","), map_res!(parse_name, str::from_utf8)) {
Ok((rem,res)) => Ok((&rem,res)),
Err(_) => Err(Err::Error(error_position!(i, ErrorKind::SeparatedList)))
}
}
#[derive(Debug,PartialEq)]
pub struct SshPacketKeyExchange<'a> {
pub cookie: &'a[u8],
pub kex_algs: &'a [u8],
pub server_host_key_algs: &'a [u8],
pub encr_algs_client_to_server: &'a [u8],
pub encr_algs_server_to_client: &'a [u8],
pub mac_algs_client_to_server: &'a [u8],
pub mac_algs_server_to_client: &'a [u8],
pub comp_algs_client_to_server: &'a [u8],
pub comp_algs_server_to_client: &'a [u8],
pub langs_client_to_server: &'a [u8],
pub langs_server_to_client: &'a [u8],
pub first_kex_packet_follows: bool,
}
named!(parse_packet_key_exchange<SshPacket>, do_parse!(
cookie: take!(16) >>
kex_algs: parse_string >>
server_host_key_algs: parse_string >>
encr_algs_client_to_server: parse_string >>
encr_algs_server_to_client: parse_string >>
mac_algs_client_to_server: parse_string >>
mac_algs_server_to_client: parse_string >>
comp_algs_client_to_server: parse_string >>
comp_algs_server_to_client: parse_string >>
langs_client_to_server: parse_string >>
langs_server_to_client: parse_string >>
first_kex_packet_follows: be_u8 >>
be_u32 >>
( SshPacket::KeyExchange(SshPacketKeyExchange {
cookie: cookie,
kex_algs: kex_algs,
server_host_key_algs: server_host_key_algs,
encr_algs_client_to_server: encr_algs_client_to_server,
encr_algs_server_to_client: encr_algs_server_to_client,
mac_algs_client_to_server: mac_algs_client_to_server,
mac_algs_server_to_client: mac_algs_server_to_client,
comp_algs_client_to_server: comp_algs_client_to_server,
comp_algs_server_to_client: comp_algs_server_to_client,
langs_client_to_server: langs_client_to_server,
langs_server_to_client: langs_server_to_client,
first_kex_packet_follows: first_kex_packet_follows > 0,
}) )
));
impl<'a> SshPacketKeyExchange<'a> {
pub fn get_kex_algs(&self) -> Result<Vec<&str>, nom::Err<&[u8]>> {
parse_name_list(self.kex_algs).map(|x| x.1)
}
pub fn get_server_host_key_algs(&self) -> Result<Vec<&str>, nom::Err<&[u8]>> {
parse_name_list(self.server_host_key_algs).map(|x| x.1)
}
pub fn get_encr_algs_client_to_server(&self) -> Result<Vec<&str>, nom::Err<&[u8]>> {
parse_name_list(self.encr_algs_client_to_server).map(|x| x.1)
}
pub fn get_encr_algs_server_to_client(&self) -> Result<Vec<&str>, nom::Err<&'a [u8]>> {
parse_name_list(self.encr_algs_server_to_client).map(|x| x.1)
}
pub fn get_mac_algs_client_to_server(&self) -> Result<Vec<&str>, nom::Err<&'a [u8]>> {
parse_name_list(self.mac_algs_client_to_server).map(|x| x.1)
}
pub fn get_mac_algs_server_to_client(&self) -> Result<Vec<&str>, nom::Err<&'a [u8]>> {
parse_name_list(self.mac_algs_server_to_client).map(|x| x.1)
}
pub fn get_comp_algs_client_to_server(&self) -> Result<Vec<&str>, nom::Err<&'a [u8]>> {
parse_name_list(self.comp_algs_client_to_server).map(|x| x.1)
}
pub fn get_comp_algs_server_to_client(&self) -> Result<Vec<&str>, nom::Err<&'a [u8]>> {
parse_name_list(self.comp_algs_server_to_client).map(|x| x.1)
}
pub fn get_langs_client_to_server(&self) -> Result<Vec<&str>, nom::Err<&'a [u8]>> {
parse_name_list(self.langs_client_to_server).map(|x| x.1)
}
pub fn get_langs_server_to_client(&self) -> Result<Vec<&str>, nom::Err<&'a [u8]>> {
parse_name_list(self.langs_server_to_client).map(|x| x.1)
}
}
#[derive(Debug,PartialEq)]
pub struct SshPacketDhInit<'a> {
pub e: &'a [u8],
}
named!(parse_packet_dh_init<SshPacket>, map!(
call!(parse_string),
|x| SshPacket::DiffieHellmanInit(SshPacketDhInit { e: x })
));
#[derive(Debug,PartialEq)]
pub struct SshPacketDhReply<'a> {
pub pubkey_and_cert: &'a [u8],
pub f: &'a [u8],
pub signature: &'a [u8],
}
named!(parse_packet_dh_reply<SshPacket>, do_parse!(
pubkey: parse_string >>
f: parse_string >>
signature: parse_string >>
( SshPacket::DiffieHellmanReply(SshPacketDhReply { pubkey_and_cert: pubkey, f: f, signature: signature }) )
));
impl<'a> SshPacketDhReply<'a> {
pub fn get_ecdsa_signature(&self) -> Result<(&str, Vec<u8>), nom::Err<&[u8]>> {
let (_,(identifier, blob)) = try!(do_parse!(self.signature,
identifier: map_res!(parse_string, str::from_utf8) >>
blob: flat_map!(call!(parse_string), pair!(parse_string, parse_string)) >>
( (identifier, blob) )
));
let mut rs = Vec::new();
rs.extend_from_slice(blob.0);
rs.extend_from_slice(blob.1);
Ok((identifier, rs))
}
}
#[derive(Debug,PartialEq)]
pub struct SshPacketDisconnect<'a> {
pub reason_code: u32,
pub description: &'a [u8],
pub lang: &'a [u8],
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SshDisconnectReason(pub u32);
#[allow(non_upper_case_globals)]
impl SshDisconnectReason {
pub const HostNotAllowedToConnect : SshDisconnectReason = SshDisconnectReason( 1);
pub const ProtocolError : SshDisconnectReason = SshDisconnectReason( 2);
pub const KeyExchangeFailed : SshDisconnectReason = SshDisconnectReason( 3);
pub const Reserved : SshDisconnectReason = SshDisconnectReason( 4);
pub const MacError : SshDisconnectReason = SshDisconnectReason( 5);
pub const CompressionError : SshDisconnectReason = SshDisconnectReason( 6);
pub const ServiceNotAvailable : SshDisconnectReason = SshDisconnectReason( 7);
pub const ProtocolVersionNotSupported : SshDisconnectReason = SshDisconnectReason( 8);
pub const HostKeyNotVerifiable : SshDisconnectReason = SshDisconnectReason( 9);
pub const ConnectionLost : SshDisconnectReason = SshDisconnectReason(10);
pub const ByApplication : SshDisconnectReason = SshDisconnectReason(11);
pub const TooManyConnections : SshDisconnectReason = SshDisconnectReason(12);
pub const AuthCancelledByUser : SshDisconnectReason = SshDisconnectReason(13);
pub const NoMoreAuthMethodsAvailable : SshDisconnectReason = SshDisconnectReason(14);
pub const IllegalUserName : SshDisconnectReason = SshDisconnectReason(15);
}
named!(parse_packet_disconnect<SshPacket>, do_parse!(
reason_code: be_u32 >>
description: parse_string >>
lang: parse_string >>
( SshPacket::Disconnect(SshPacketDisconnect { reason_code: reason_code, description: description, lang: lang}) )
));
impl<'a> SshPacketDisconnect<'a> {
pub fn get_description(&self) -> Result<&str, str::Utf8Error> {
str::from_utf8(self.description)
}
pub fn get_reason(&self) -> SshDisconnectReason {
SshDisconnectReason(self.reason_code)
}
}
#[derive(Debug,PartialEq)]
pub struct SshPacketDebug<'a> {
pub always_display: bool,
pub message: &'a [u8],
pub lang: &'a [u8],
}
named!(parse_packet_debug<SshPacket>, do_parse!(
display: be_u8 >>
message: parse_string >>
lang: parse_string >>
( SshPacket::Debug(SshPacketDebug { always_display: display > 0, message: message, lang: lang}) )
));
impl<'a> SshPacketDebug<'a> {
pub fn get_message(&self) -> Result<&str, str::Utf8Error> {
str::from_utf8(self.message)
}
}
#[derive(Debug,PartialEq)]
pub enum SshPacket<'a> {
Disconnect(SshPacketDisconnect<'a>),
Ignore(&'a [u8]),
Unimplemented(u32),
Debug(SshPacketDebug<'a>),
ServiceRequest(&'a [u8]),
ServiceAccept(&'a [u8]),
KeyExchange(SshPacketKeyExchange<'a>),
NewKeys,
DiffieHellmanInit(SshPacketDhInit<'a>),
DiffieHellmanReply(SshPacketDhReply<'a>),
}
pub fn parse_ssh_packet(i: &[u8]) -> IResult<&[u8], (SshPacket, &[u8])> {
do_parse!(i,
packet_length: be_u32 >>
padding_length: be_u8 >>
error_if!(padding_length as u32 + 1 > packet_length, ErrorKind::Custom(128)) >>
payload: flat_map!(
take!(packet_length - padding_length as u32 - 1),
switch!(be_u8,
1 => call!(parse_packet_disconnect) |
2 => map!(parse_string, |x| SshPacket::Ignore(x)) |
3 => map!(be_u32, |x| SshPacket::Unimplemented(x)) |
4 => call!(parse_packet_debug) |
5 => map!(parse_string, |x| SshPacket::ServiceRequest(x)) |
6 => map!(parse_string, |x| SshPacket::ServiceAccept(x)) |
20 => call!(parse_packet_key_exchange) |
21 => value!(SshPacket::NewKeys) |
30 => call!(parse_packet_dh_init) |
31 => call!(parse_packet_dh_reply)
)
) >>
padding: take!(padding_length) >>
( (payload, padding) )
)
}
#[cfg(test)]
mod tests {
use super::*;
use nom::{ErrorKind,Err};
#[test]
fn test_name() {
let res = parse_name(CompleteByteSlice(b"ssh-rsa"));
let expected = Ok((CompleteByteSlice(&b""[..]),&b"ssh-rsa"[..]));
assert_eq!(res, expected);
}
#[test]
fn test_empty_name_list() {
let res = parse_name_list(b"");
let expected = Err(Err::Error(error_position!(&b""[..], ErrorKind::SeparatedList)));
assert_eq!(res, expected);
}
#[test]
fn test_one_name_list() {
let res = parse_name_list(b"ssh-rsa");
let expected = Ok((&b""[..],vec!["ssh-rsa"]));
assert_eq!(res, expected);
}
#[test]
fn test_two_names_list() {
let res = parse_name_list(b"ssh-rsa,ssh-ecdsa");
let expected = Ok((&b""[..],vec!["ssh-rsa","ssh-ecdsa"]));
assert_eq!(res, expected);
}
}