openssh_mux_client/
request.rs

1#![forbid(unsafe_code)]
2
3use super::{constants, default_config, utils::MaybeOwned, NonZeroByteSlice, NonZeroByteVec};
4
5use std::{borrow::Cow, path::Path};
6
7use cfg_if::cfg_if;
8use serde::{Serialize, Serializer};
9use typed_builder::TypedBuilder;
10
11#[derive(Copy, Clone, Debug)]
12pub(crate) enum Request {
13    /// Response with `Response::Hello`.
14    Hello { version: u32 },
15
16    /// Server replied with `Response::Alive`.
17    AliveCheck { request_id: u32 },
18
19    /// For opening a new multiplexed session in passenger mode,
20    /// send this variant and then sends stdin, stdout and stderr fd.
21    ///
22    /// If successful, the server will reply with `Response::SessionOpened`.
23    ///
24    /// Otherwise it will reply with an error:
25    ///  - `Response::PermissionDenied`;
26    ///  - `Response::Failure`.
27    ///
28    /// The client now waits for the session to end. When it does, the server
29    /// will send `Response::ExitMessage`.
30    ///
31    /// Two additional cases that the client must cope with are it receiving
32    /// a signal itself and the server disconnecting without sending an exit message.
33    ///
34    /// A master may also send a `Response::TtyAllocFail` before
35    /// `Response::ExitMessage` if remote TTY allocation was unsuccessful.
36    ///
37    /// The client may use this to return its local tty to "cooked" mode.
38    NewSession {
39        request_id: u32,
40        session: SessionZeroCopy,
41    },
42
43    /// A server may reply with `Response::Ok`, `Response::RemotePort`,
44    /// `Response::PermissionDenied`, or `Response::Failure`.
45    ///
46    /// For dynamically allocated listen port the server replies with
47    /// `Request::RemotePort`.
48    OpenFwd { request_id: u32, fwd_mode: u32 },
49
50    /// A server may reply with `Response::Ok`, `Response::PermissionDenied`,
51    /// or `Response::Failure`.
52    CloseFwd { request_id: u32, fwd_mode: u32 },
53
54    /// A client may request the master to stop accepting new multiplexing requests
55    /// and remove its listener socket.
56    ///
57    /// A server may reply with `Response::Ok`, `Response::PermissionDenied` or
58    /// `Response::Failure`.
59    StopListening { request_id: u32 },
60}
61impl Serialize for Request {
62    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
63        use constants::*;
64        use Request::*;
65
66        match self {
67            Hello { version } => {
68                serializer.serialize_newtype_variant("Request", MUX_MSG_HELLO, "Hello", version)
69            }
70            AliveCheck { request_id } => serializer.serialize_newtype_variant(
71                "Request",
72                MUX_C_ALIVE_CHECK,
73                "AliveCheck",
74                request_id,
75            ),
76            NewSession {
77                request_id,
78                session,
79            } => serializer.serialize_newtype_variant(
80                "Request",
81                MUX_C_NEW_SESSION,
82                "NewSession",
83                &(*request_id, "", *session),
84            ),
85            OpenFwd {
86                request_id,
87                fwd_mode,
88            } => serializer.serialize_newtype_variant(
89                "Request",
90                MUX_C_OPEN_FWD,
91                "OpenFwd",
92                &(*request_id, fwd_mode),
93            ),
94            CloseFwd {
95                request_id,
96                fwd_mode,
97            } => serializer.serialize_newtype_variant(
98                "Request",
99                MUX_C_CLOSE_FWD,
100                "CloseFwd",
101                &(*request_id, fwd_mode),
102            ),
103            StopListening { request_id } => serializer.serialize_newtype_variant(
104                "Request",
105                MUX_C_STOP_LISTENING,
106                "StopListening",
107                request_id,
108            ),
109        }
110    }
111}
112
113/// Zero copy version of [`Session`]
114#[derive(Copy, Clone, Debug, Serialize)]
115pub(crate) struct SessionZeroCopy {
116    pub tty: bool,
117
118    pub x11_forwarding: bool,
119
120    pub agent: bool,
121
122    pub subsystem: bool,
123
124    pub escape_ch: char,
125}
126
127#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, TypedBuilder)]
128#[builder(doc)]
129pub struct Session<'a> {
130    #[builder(default = false)]
131    pub tty: bool,
132
133    #[builder(default = false)]
134    pub x11_forwarding: bool,
135
136    #[builder(default = false)]
137    pub agent: bool,
138
139    #[builder(default = false)]
140    pub subsystem: bool,
141
142    /// Set to `0xffffffff`(`char::MAX`) to disable escape character
143    #[builder(default = char::MAX)]
144    pub escape_ch: char,
145
146    /// Generally set to `$TERM`.
147    #[builder(default_code = r#"Cow::Borrowed(default_config::get_term())"#)]
148    pub term: Cow<'a, NonZeroByteSlice>,
149    pub cmd: Cow<'a, NonZeroByteSlice>,
150}
151
152#[derive(Copy, Clone, Debug)]
153pub enum Fwd<'a> {
154    Local {
155        listen_socket: &'a Socket<'a>,
156        connect_socket: &'a Socket<'a>,
157    },
158    Remote {
159        listen_socket: &'a Socket<'a>,
160        connect_socket: &'a Socket<'a>,
161    },
162    Dynamic {
163        listen_socket: &'a Socket<'a>,
164    },
165}
166impl<'a> Fwd<'a> {
167    pub(crate) fn as_serializable(&self) -> (u32, &'a Socket<'a>, MaybeOwned<'a, Socket<'a>>) {
168        use Fwd::*;
169
170        match *self {
171            Local {
172                listen_socket,
173                connect_socket,
174            } => (
175                constants::MUX_FWD_LOCAL,
176                listen_socket,
177                MaybeOwned::Borrowed(connect_socket),
178            ),
179            Remote {
180                listen_socket,
181                connect_socket,
182            } => (
183                constants::MUX_FWD_REMOTE,
184                listen_socket,
185                MaybeOwned::Borrowed(connect_socket),
186            ),
187            Dynamic { listen_socket } => (
188                constants::MUX_FWD_DYNAMIC,
189                listen_socket,
190                MaybeOwned::Owned(Socket::UnixSocket {
191                    path: Path::new("").into(),
192                }),
193            ),
194        }
195    }
196}
197impl<'a> Serialize for Fwd<'a> {
198    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
199        self.as_serializable().serialize(serializer)
200    }
201}
202
203trait PathExt {
204    fn to_non_null_bytes(&self) -> Cow<'_, NonZeroByteSlice>;
205
206    fn to_bytes(&self) -> Cow<'_, [u8]>;
207
208    fn to_string_lossy_and_as_bytes(&self) -> Cow<'_, [u8]>;
209}
210
211impl PathExt for Path {
212    fn to_non_null_bytes(&self) -> Cow<'_, NonZeroByteSlice> {
213        match self.to_bytes() {
214            Cow::Borrowed(slice) => NonZeroByteVec::from_bytes_slice_lossy(slice),
215            Cow::Owned(bytes) => Cow::Owned(NonZeroByteVec::from_bytes_remove_nul(bytes)),
216        }
217    }
218
219    fn to_bytes(&self) -> Cow<'_, [u8]> {
220        cfg_if! {
221            if #[cfg(unix)] {
222                use std::os::unix::ffi::OsStrExt;
223
224                Cow::Borrowed(self.as_os_str().as_bytes())
225            } else {
226                self.to_string_lossy_and_as_bytes()
227            }
228        }
229    }
230
231    fn to_string_lossy_and_as_bytes(&self) -> Cow<'_, [u8]> {
232        match self.to_string_lossy() {
233            Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()),
234            Cow::Owned(s) => Cow::Owned(s.into_bytes()),
235        }
236    }
237}
238
239#[derive(Clone, Debug, Eq, PartialEq, Hash)]
240pub enum Socket<'a> {
241    UnixSocket { path: Cow<'a, Path> },
242    TcpSocket { port: u32, host: Cow<'a, str> },
243}
244impl Socket<'_> {
245    pub(crate) fn as_serializable(&self) -> (Cow<'_, NonZeroByteSlice>, u32) {
246        use Socket::*;
247
248        let unix_socket_port: i32 = -2;
249
250        match self {
251            // Serialize impl for Path calls to_str and ret err if failed,
252            // so calling to_string_lossy is OK as it does not break backward
253            // compatibility.
254            UnixSocket { path } => (path.to_non_null_bytes(), unix_socket_port as u32),
255            TcpSocket { port, host } => (
256                NonZeroByteVec::from_bytes_slice_lossy(host.as_bytes()),
257                *port,
258            ),
259        }
260    }
261}
262impl<'a> Serialize for Socket<'a> {
263    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
264        self.as_serializable().serialize(serializer)
265    }
266}