bssh_russh/
lib_inner.rs

1use std::convert::TryFrom;
2use std::fmt::{Debug, Display, Formatter};
3use std::future::{Future, Pending};
4
5use futures::future::Either as EitherFuture;
6use log::{debug, warn};
7use parsing::ChannelOpenConfirmation;
8pub use russh_cryptovec::CryptoVec;
9use ssh_encoding::{Decode, Encode};
10use thiserror::Error;
11
12#[cfg(test)]
13mod tests;
14
15mod auth;
16
17mod cert;
18/// Cipher names
19pub mod cipher;
20/// Compression algorithm names
21pub mod compression;
22/// Key exchange algorithm names
23pub mod kex;
24/// MAC algorithm names
25pub mod mac;
26
27pub mod keys;
28
29mod msg;
30mod negotiation;
31mod ssh_read;
32mod sshbuffer;
33
34pub use negotiation::{Names, Preferred};
35
36mod pty;
37
38pub use pty::Pty;
39pub use sshbuffer::SshId;
40
41mod helpers;
42
43pub(crate) use helpers::map_err;
44
45macro_rules! push_packet {
46    ( $buffer:expr, $x:expr ) => {{
47        use byteorder::{BigEndian, ByteOrder};
48        let i0 = $buffer.len();
49        $buffer.extend(b"\0\0\0\0");
50        let x = $x;
51        let i1 = $buffer.len();
52        use std::ops::DerefMut;
53        let buf = $buffer.deref_mut();
54        #[allow(clippy::indexing_slicing)] // length checked
55        BigEndian::write_u32(&mut buf[i0..], (i1 - i0 - 4) as u32);
56        x
57    }};
58}
59
60mod channels;
61pub use channels::{Channel, ChannelMsg, ChannelReadHalf, ChannelStream, ChannelWriteHalf};
62
63mod parsing;
64mod session;
65
66/// Server side of this library.
67#[cfg(not(target_arch = "wasm32"))]
68pub mod server;
69
70/// Client side of this library.
71pub mod client;
72
73#[derive(Debug)]
74pub enum AlgorithmKind {
75    Kex,
76    Key,
77    Cipher,
78    Compression,
79    Mac,
80}
81
82#[derive(Debug, Error)]
83pub enum Error {
84    /// The key file could not be parsed.
85    #[error("Could not read key")]
86    CouldNotReadKey,
87
88    /// Unspecified problem with the beginning of key exchange.
89    #[error("Key exchange init failed")]
90    KexInit,
91
92    /// Unknown algorithm name.
93    #[error("Unknown algorithm")]
94    UnknownAlgo,
95
96    /// No common algorithm found during key exchange.
97    #[error("No common {kind:?} algorithm - ours: {ours:?}, theirs: {theirs:?}")]
98    NoCommonAlgo {
99        kind: AlgorithmKind,
100        ours: Vec<String>,
101        theirs: Vec<String>,
102    },
103
104    /// Invalid SSH version string.
105    #[error("invalid SSH version string")]
106    Version,
107
108    /// Error during key exchange.
109    #[error("Key exchange failed")]
110    Kex,
111
112    /// Invalid packet authentication code.
113    #[error("Wrong packet authentication code")]
114    PacketAuth,
115
116    /// The protocol is in an inconsistent state.
117    #[error("Inconsistent state of the protocol")]
118    Inconsistent,
119
120    /// The client is not yet authenticated.
121    #[error("Not yet authenticated")]
122    NotAuthenticated,
123
124    /// The client has presented an unsupported authentication method.
125    #[error("Unsupported authentication method")]
126    UnsupportedAuthMethod,
127
128    /// Index out of bounds.
129    #[error("Index out of bounds")]
130    IndexOutOfBounds,
131
132    /// Unknown server key.
133    #[error("Unknown server key")]
134    UnknownKey,
135
136    /// The server provided a wrong signature.
137    #[error("Wrong server signature")]
138    WrongServerSig,
139
140    /// Excessive packet size.
141    #[error("Bad packet size: {0}")]
142    PacketSize(usize),
143
144    /// Message received/sent on unopened channel.
145    #[error("Channel not open")]
146    WrongChannel,
147
148    /// Server refused to open a channel.
149    #[error("Failed to open channel ({0:?})")]
150    ChannelOpenFailure(ChannelOpenFailure),
151
152    /// Disconnected
153    #[error("Disconnected")]
154    Disconnect,
155
156    /// No home directory found when trying to learn new host key.
157    #[error("No home directory when saving host key")]
158    NoHomeDir,
159
160    /// Remote key changed, this could mean a man-in-the-middle attack
161    /// is being performed on the connection.
162    #[error("Key changed, line {}", line)]
163    KeyChanged { line: usize },
164
165    /// Connection closed by the remote side.
166    #[error("Connection closed by the remote side")]
167    HUP,
168
169    /// Connection timeout.
170    #[error("Connection timeout")]
171    ConnectionTimeout,
172
173    /// Keepalive timeout.
174    #[error("Keepalive timeout")]
175    KeepaliveTimeout,
176
177    /// Inactivity timeout.
178    #[error("Inactivity timeout")]
179    InactivityTimeout,
180
181    /// Missing authentication method.
182    #[error("No authentication method")]
183    NoAuthMethod,
184
185    #[error("Channel send error")]
186    SendError,
187
188    #[error("Pending buffer limit reached")]
189    Pending,
190
191    #[error("Failed to decrypt a packet")]
192    DecryptionError,
193
194    #[error("The request was rejected by the other party")]
195    RequestDenied,
196
197    #[error(transparent)]
198    Keys(#[from] crate::keys::Error),
199
200    #[error(transparent)]
201    IO(#[from] std::io::Error),
202
203    #[error(transparent)]
204    Utf8(#[from] std::str::Utf8Error),
205
206    #[error(transparent)]
207    #[cfg(feature = "flate2")]
208    Compress(#[from] flate2::CompressError),
209
210    #[error(transparent)]
211    #[cfg(feature = "flate2")]
212    Decompress(#[from] flate2::DecompressError),
213
214    #[error(transparent)]
215    Join(#[from] russh_util::runtime::JoinError),
216
217    #[error(transparent)]
218    Elapsed(#[from] tokio::time::error::Elapsed),
219
220    #[error("Violation detected during strict key exchange, message {message_type} at seq no {sequence_number}")]
221    StrictKeyExchangeViolation {
222        message_type: u8,
223        sequence_number: usize,
224    },
225
226    #[error("Signature: {0}")]
227    Signature(#[from] signature::Error),
228
229    #[error("SshKey: {0}")]
230    SshKey(#[from] ssh_key::Error),
231
232    #[error("SshEncoding: {0}")]
233    SshEncoding(#[from] ssh_encoding::Error),
234
235    #[error("Invalid config: {0}")]
236    InvalidConfig(String),
237
238    /// This error occurs when the channel is closed and there are no remaining messages in the channel buffer.
239    /// This is common in SSH-Agent, for example when the Agent client directly rejects an authorization request.
240    #[error("Unable to receive more messages from the channel")]
241    RecvError,
242}
243
244pub(crate) fn strict_kex_violation(message_type: u8, sequence_number: usize) -> crate::Error {
245    warn!(
246        "strict kex violated at sequence no. {sequence_number:?}, message type: {message_type:?}"
247    );
248    crate::Error::StrictKeyExchangeViolation {
249        message_type,
250        sequence_number,
251    }
252}
253
254#[derive(Debug, Error)]
255#[error("Could not reach the event loop")]
256pub struct SendError {}
257
258/// The number of bytes read/written, and the number of seconds before a key
259/// re-exchange is requested.
260#[derive(Debug, Clone)]
261pub struct Limits {
262    pub rekey_write_limit: usize,
263    pub rekey_read_limit: usize,
264    pub rekey_time_limit: std::time::Duration,
265}
266
267impl Limits {
268    /// Create a new `Limits`, checking that the given bounds cannot lead to
269    /// nonce reuse.
270    pub fn new(write_limit: usize, read_limit: usize, time_limit: std::time::Duration) -> Limits {
271        assert!(write_limit <= 1 << 30 && read_limit <= 1 << 30);
272        Limits {
273            rekey_write_limit: write_limit,
274            rekey_read_limit: read_limit,
275            rekey_time_limit: time_limit,
276        }
277    }
278}
279
280impl Default for Limits {
281    fn default() -> Self {
282        // Following the recommendations of
283        // https://tools.ietf.org/html/rfc4253#section-9
284        Limits {
285            rekey_write_limit: 1 << 30, // 1 Gb
286            rekey_read_limit: 1 << 30,  // 1 Gb
287            rekey_time_limit: std::time::Duration::from_secs(3600),
288        }
289    }
290}
291
292pub use auth::{AgentAuthError, MethodKind, MethodSet, Signer};
293
294/// A reason for disconnection.
295#[allow(missing_docs)] // This should be relatively self-explanatory.
296#[allow(clippy::manual_non_exhaustive)]
297#[derive(Debug)]
298pub enum Disconnect {
299    HostNotAllowedToConnect = 1,
300    ProtocolError = 2,
301    KeyExchangeFailed = 3,
302    #[doc(hidden)]
303    Reserved = 4,
304    MACError = 5,
305    CompressionError = 6,
306    ServiceNotAvailable = 7,
307    ProtocolVersionNotSupported = 8,
308    HostKeyNotVerifiable = 9,
309    ConnectionLost = 10,
310    ByApplication = 11,
311    TooManyConnections = 12,
312    AuthCancelledByUser = 13,
313    NoMoreAuthMethodsAvailable = 14,
314    IllegalUserName = 15,
315}
316
317impl TryFrom<u32> for Disconnect {
318    type Error = crate::Error;
319
320    fn try_from(value: u32) -> Result<Self, Self::Error> {
321        Ok(match value {
322            1 => Self::HostNotAllowedToConnect,
323            2 => Self::ProtocolError,
324            3 => Self::KeyExchangeFailed,
325            4 => Self::Reserved,
326            5 => Self::MACError,
327            6 => Self::CompressionError,
328            7 => Self::ServiceNotAvailable,
329            8 => Self::ProtocolVersionNotSupported,
330            9 => Self::HostKeyNotVerifiable,
331            10 => Self::ConnectionLost,
332            11 => Self::ByApplication,
333            12 => Self::TooManyConnections,
334            13 => Self::AuthCancelledByUser,
335            14 => Self::NoMoreAuthMethodsAvailable,
336            15 => Self::IllegalUserName,
337            _ => return Err(crate::Error::Inconsistent),
338        })
339    }
340}
341
342/// The type of signals that can be sent to a remote process. If you
343/// plan to use custom signals, read [the
344/// RFC](https://tools.ietf.org/html/rfc4254#section-6.10) to
345/// understand the encoding.
346#[allow(missing_docs)]
347// This should be relatively self-explanatory.
348#[derive(Debug, Clone)]
349pub enum Sig {
350    ABRT,
351    ALRM,
352    FPE,
353    HUP,
354    ILL,
355    INT,
356    KILL,
357    PIPE,
358    QUIT,
359    SEGV,
360    TERM,
361    USR1,
362    Custom(String),
363}
364
365impl Sig {
366    fn name(&self) -> &str {
367        match *self {
368            Sig::ABRT => "ABRT",
369            Sig::ALRM => "ALRM",
370            Sig::FPE => "FPE",
371            Sig::HUP => "HUP",
372            Sig::ILL => "ILL",
373            Sig::INT => "INT",
374            Sig::KILL => "KILL",
375            Sig::PIPE => "PIPE",
376            Sig::QUIT => "QUIT",
377            Sig::SEGV => "SEGV",
378            Sig::TERM => "TERM",
379            Sig::USR1 => "USR1",
380            Sig::Custom(ref c) => c,
381        }
382    }
383    fn from_name(name: &str) -> Sig {
384        match name {
385            "ABRT" => Sig::ABRT,
386            "ALRM" => Sig::ALRM,
387            "FPE" => Sig::FPE,
388            "HUP" => Sig::HUP,
389            "ILL" => Sig::ILL,
390            "INT" => Sig::INT,
391            "KILL" => Sig::KILL,
392            "PIPE" => Sig::PIPE,
393            "QUIT" => Sig::QUIT,
394            "SEGV" => Sig::SEGV,
395            "TERM" => Sig::TERM,
396            "USR1" => Sig::USR1,
397            x => Sig::Custom(x.to_string()),
398        }
399    }
400}
401
402/// Reason for not being able to open a channel.
403#[derive(Debug, Copy, Clone, PartialEq, Eq)]
404#[allow(missing_docs)]
405pub enum ChannelOpenFailure {
406    AdministrativelyProhibited = 1,
407    ConnectFailed = 2,
408    UnknownChannelType = 3,
409    ResourceShortage = 4,
410    Unknown = 0,
411}
412
413impl ChannelOpenFailure {
414    fn from_u32(x: u32) -> Option<ChannelOpenFailure> {
415        match x {
416            1 => Some(ChannelOpenFailure::AdministrativelyProhibited),
417            2 => Some(ChannelOpenFailure::ConnectFailed),
418            3 => Some(ChannelOpenFailure::UnknownChannelType),
419            4 => Some(ChannelOpenFailure::ResourceShortage),
420            _ => None,
421        }
422    }
423}
424
425#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
426/// The identifier of a channel.
427pub struct ChannelId(u32);
428
429impl Decode for ChannelId {
430    type Error = ssh_encoding::Error;
431
432    fn decode(reader: &mut impl ssh_encoding::Reader) -> Result<Self, Self::Error> {
433        Ok(Self(u32::decode(reader)?))
434    }
435}
436
437impl Encode for ChannelId {
438    fn encoded_len(&self) -> Result<usize, ssh_encoding::Error> {
439        self.0.encoded_len()
440    }
441
442    fn encode(&self, writer: &mut impl ssh_encoding::Writer) -> Result<(), ssh_encoding::Error> {
443        self.0.encode(writer)
444    }
445}
446
447impl From<ChannelId> for u32 {
448    fn from(c: ChannelId) -> u32 {
449        c.0
450    }
451}
452
453impl Display for ChannelId {
454    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
455        write!(f, "{}", self.0)
456    }
457}
458
459/// The parameters of a channel.
460#[derive(Debug)]
461pub(crate) struct ChannelParams {
462    recipient_channel: u32,
463    sender_channel: ChannelId,
464    recipient_window_size: u32,
465    sender_window_size: u32,
466    recipient_maximum_packet_size: u32,
467    sender_maximum_packet_size: u32,
468    /// Has the other side confirmed the channel?
469    pub confirmed: bool,
470    #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
471    wants_reply: bool,
472    /// (buffer, extended stream #, data offset in buffer)
473    pending_data: std::collections::VecDeque<(CryptoVec, Option<u32>, usize)>,
474    pending_eof: bool,
475    pending_close: bool,
476}
477
478impl ChannelParams {
479    pub fn confirm(&mut self, c: &ChannelOpenConfirmation) {
480        self.recipient_channel = c.sender_channel; // "sender" is the sender of the confirmation
481        self.recipient_window_size = c.initial_window_size;
482        self.recipient_maximum_packet_size = c.maximum_packet_size;
483        self.confirmed = true;
484    }
485}
486
487/// Returns `f(val)` if `val` it is [Some], or a forever pending [Future] if it is [None].
488pub(crate) fn future_or_pending<R, F: Future<Output = R>, T>(
489    val: Option<T>,
490    f: impl FnOnce(T) -> F,
491) -> EitherFuture<Pending<R>, F> {
492    match val {
493        None => EitherFuture::Left(core::future::pending()),
494        Some(x) => EitherFuture::Right(f(x)),
495    }
496}