ssh2/
lib.rs

1//! Rust bindings to libssh2, an SSH client library.
2//!
3//! This library intends to provide a safe interface to the libssh2 library. It
4//! will build the library if it's not available on the local system, and
5//! otherwise link to an installed copy.
6//!
7//! Note that libssh2 only supports SSH *clients*, not SSH *servers*.
8//! Additionally it only supports protocol v2, not protocol v1.
9//!
10//! # Examples
11//!
12//! ## Inspecting ssh-agent
13//!
14//! ```no_run
15//! use ssh2::Session;
16//!
17//! // Almost all APIs require a `Session` to be available
18//! let sess = Session::new().unwrap();
19//! let mut agent = sess.agent().unwrap();
20//!
21//! // Connect the agent and request a list of identities
22//! agent.connect().unwrap();
23//! agent.list_identities().unwrap();
24//!
25//! for identity in agent.identities().unwrap() {
26//!     println!("{}", identity.comment());
27//!     let pubkey = identity.blob();
28//! }
29//! ```
30//!
31//! ## Authenticating with ssh-agent
32//!
33//! ```no_run
34//! use std::net::TcpStream;
35//! use ssh2::Session;
36//!
37//! // Connect to the local SSH server
38//! let tcp = TcpStream::connect("127.0.0.1:22").unwrap();
39//! let mut sess = Session::new().unwrap();
40//! sess.set_tcp_stream(tcp);
41//! sess.handshake().unwrap();
42//!
43//! // Try to authenticate with the first identity in the agent.
44//! sess.userauth_agent("username").unwrap();
45//!
46//! // Make sure we succeeded
47//! assert!(sess.authenticated());
48//! ```
49//!
50//! ## Authenticating with a password
51//!
52//! ```no_run
53//! use std::net::TcpStream;
54//! use ssh2::Session;
55//!
56//! // Connect to the local SSH server
57//! let tcp = TcpStream::connect("127.0.0.1:22").unwrap();
58//! let mut sess = Session::new().unwrap();
59//! sess.set_tcp_stream(tcp);
60//! sess.handshake().unwrap();
61//!
62//! sess.userauth_password("username", "password").unwrap();
63//! assert!(sess.authenticated());
64//! ```
65//!
66//! ## Run a command
67//!
68//! ```no_run
69//! use std::io::prelude::*;
70//! use std::net::TcpStream;
71//! use ssh2::Session;
72//!
73//! // Connect to the local SSH server
74//! let tcp = TcpStream::connect("127.0.0.1:22").unwrap();
75//! let mut sess = Session::new().unwrap();
76//! sess.set_tcp_stream(tcp);
77//! sess.handshake().unwrap();
78//! sess.userauth_agent("username").unwrap();
79//!
80//! let mut channel = sess.channel_session().unwrap();
81//! channel.exec("ls").unwrap();
82//! let mut s = String::new();
83//! channel.read_to_string(&mut s).unwrap();
84//! println!("{}", s);
85//! channel.wait_close().unwrap();
86//! println!("{}", channel.exit_status().unwrap());
87//! ```
88//!
89//! ## Upload a file
90//!
91//! ```no_run
92//! use std::io::prelude::*;
93//! use std::net::TcpStream;
94//! use std::path::Path;
95//! use ssh2::Session;
96//!
97//! // Connect to the local SSH server
98//! let tcp = TcpStream::connect("127.0.0.1:22").unwrap();
99//! let mut sess = Session::new().unwrap();
100//! sess.set_tcp_stream(tcp);
101//! sess.handshake().unwrap();
102//! sess.userauth_agent("username").unwrap();
103//!
104//! // Write the file
105//! let mut remote_file = sess.scp_send(Path::new("remote"),
106//!                                     0o644, 10, None).unwrap();
107//! remote_file.write(b"1234567890").unwrap();
108//! // Close the channel and wait for the whole content to be transferred
109//! remote_file.send_eof().unwrap();
110//! remote_file.wait_eof().unwrap();
111//! remote_file.close().unwrap();
112//! remote_file.wait_close().unwrap();
113//! ```
114//!
115//! ## Download a file
116//!
117//! ```no_run
118//! use std::io::prelude::*;
119//! use std::net::TcpStream;
120//! use std::path::Path;
121//! use ssh2::Session;
122//!
123//! // Connect to the local SSH server
124//! let tcp = TcpStream::connect("127.0.0.1:22").unwrap();
125//! let mut sess = Session::new().unwrap();
126//! sess.set_tcp_stream(tcp);
127//! sess.handshake().unwrap();
128//! sess.userauth_agent("username").unwrap();
129//!
130//! let (mut remote_file, stat) = sess.scp_recv(Path::new("remote")).unwrap();
131//! println!("remote file size: {}", stat.size());
132//! let mut contents = Vec::new();
133//! remote_file.read_to_end(&mut contents).unwrap();
134//!
135//! // Close the channel and wait for the whole content to be tranferred
136//! remote_file.send_eof().unwrap();
137//! remote_file.wait_eof().unwrap();
138//! remote_file.close().unwrap();
139//! remote_file.wait_close().unwrap();
140//! ```
141//!
142//! ## Execute a Netconf XML payload
143//!
144//! ```no_run
145//! use ssh2::{Channel, Session};
146//! use std::error::Error;
147//! use std::io::prelude::*;
148//! use std::net::TcpStream;
149//!
150//! const HELLO: &str = "<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">
151//!   <capabilities>
152//!     <capability>urn:ietf:params:netconf:base:1.1</capability>
153//!   </capabilities>
154//! </hello>
155//! ]]>]]>";
156//!
157//! const PAYLOAD: &str = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
158//!     <rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.1\" message-id=\"2\">
159//!     <cli xmlns=\"http://cisco.com/ns/yang/cisco-nx-os-device\"><mode>EXEC</mode><cmdline>show version</cmdline></cli>
160//! </rpc>";
161//!
162//! fn read(channel: &mut Channel) -> Result<String, Box<dyn Error>> {
163//!     let mut result = String::new();
164//!     loop {
165//!         // If you plan to use this, be aware that reading 1 byte at a time is terribly
166//!         // inefficient and should be optimized for your usecase. This is just an example.
167//!         let mut buffer = [1u8; 1];
168//!         let bytes_read = channel.read(&mut buffer[..])?;
169//!         let s = String::from_utf8_lossy(&buffer[..bytes_read]);
170//!         result.push_str(&s);
171//!         if result.ends_with("]]>]]>") {
172//!             println!("Found netconf 1.0 terminator, breaking read loop");
173//!             break;
174//!         }
175//!         if result.ends_with("##") {
176//!             println!("Found netconf 1.1 terminator, breaking read loop");
177//!             break;
178//!         }
179//!         if bytes_read == 0 || channel.eof() {
180//!             println!("Buffer is empty, SSH channel read terminated");
181//!             break;
182//!         }
183//!     }
184//!     Ok(result)
185//! }
186//!
187//! fn main() -> Result<(), Box<dyn Error>> {
188//!     let tcp = TcpStream::connect("127.0.0.1:830")?;
189//!     let mut sess = Session::new()?;
190//!     sess.set_tcp_stream(tcp);
191//!     sess.handshake().unwrap();
192//!     sess.userauth_password("user", "pass")?;
193//!
194//!     let mut channel = sess.channel_session()?;
195//!     channel.subsystem("netconf")?;
196//!     let result = read(&mut channel)?;
197//!     println!("Result from connection:\n{}", result);
198//!
199//!     let payload = format!("{}\n#{}\n{}\n##\n", HELLO, PAYLOAD.len(), PAYLOAD);
200//!     let a = channel.write(payload.as_bytes())?;
201//!     println!("Written {} bytes payload", a);
202//!     let result = read(&mut channel)?;
203//!     println!("Result from payload execution:\n{}", result);
204//!
205//!     channel.send_eof()?;
206//!     channel.wait_eof()?;
207//!     channel.close()?;
208//!     channel.wait_close()?;
209//!     Ok(())
210//! }
211//! ```
212
213#![doc(html_root_url = "https://docs.rs/ssh2")]
214#![allow(trivial_numeric_casts)]
215#![deny(missing_docs, unused_results)]
216#![cfg_attr(test, deny(warnings))]
217
218extern crate libc;
219extern crate libssh2_sys as raw;
220#[macro_use]
221extern crate bitflags;
222extern crate parking_lot;
223
224use std::ffi::CStr;
225
226pub use agent::{Agent, PublicKey};
227pub use channel::{Channel, ExitSignal, ReadWindow, Stream, WriteWindow};
228pub use error::{Error, ErrorCode};
229pub use knownhosts::{Host, KnownHosts};
230pub use listener::Listener;
231use session::SessionInner;
232pub use session::{BlockDirections, KeyboardInteractivePrompt, Prompt, ScpFileStat, Session, TraceFlags};
233pub use sftp::{File, FileStat, FileType, OpenType};
234pub use sftp::{OpenFlags, RenameFlags, Sftp};
235pub use DisconnectCode::{AuthCancelledByUser, TooManyConnections};
236pub use DisconnectCode::{ByApplication, ConnectionLost, HostKeyNotVerifiable};
237pub use DisconnectCode::{CompressionError, KeyExchangeFailed, MacError, Reserved};
238pub use DisconnectCode::{HostNotAllowedToConnect, ProtocolError};
239pub use DisconnectCode::{IllegalUserName, NoMoreAuthMethodsAvailable};
240pub use DisconnectCode::{ProtocolVersionNotSupported, ServiceNotAvailable};
241
242mod agent;
243mod channel;
244mod error;
245mod knownhosts;
246mod listener;
247mod session;
248mod sftp;
249mod util;
250
251/// Initialize the libssh2 library.
252///
253/// This is optional, it is lazily invoked.
254pub fn init() {
255    raw::init();
256}
257
258unsafe fn opt_bytes<'a, T>(_: &'a T, c: *const libc::c_char) -> Option<&'a [u8]> {
259    if c.is_null() {
260        None
261    } else {
262        Some(CStr::from_ptr(c).to_bytes())
263    }
264}
265
266#[allow(missing_docs)]
267#[derive(Copy, Clone)]
268pub enum DisconnectCode {
269    HostNotAllowedToConnect = raw::SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT as isize,
270    ProtocolError = raw::SSH_DISCONNECT_PROTOCOL_ERROR as isize,
271    KeyExchangeFailed = raw::SSH_DISCONNECT_KEY_EXCHANGE_FAILED as isize,
272    Reserved = raw::SSH_DISCONNECT_RESERVED as isize,
273    MacError = raw::SSH_DISCONNECT_MAC_ERROR as isize,
274    CompressionError = raw::SSH_DISCONNECT_COMPRESSION_ERROR as isize,
275    ServiceNotAvailable = raw::SSH_DISCONNECT_SERVICE_NOT_AVAILABLE as isize,
276    ProtocolVersionNotSupported = raw::SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED as isize,
277    HostKeyNotVerifiable = raw::SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE as isize,
278    ConnectionLost = raw::SSH_DISCONNECT_CONNECTION_LOST as isize,
279    ByApplication = raw::SSH_DISCONNECT_BY_APPLICATION as isize,
280    TooManyConnections = raw::SSH_DISCONNECT_TOO_MANY_CONNECTIONS as isize,
281    AuthCancelledByUser = raw::SSH_DISCONNECT_AUTH_CANCELLED_BY_USER as isize,
282    NoMoreAuthMethodsAvailable = raw::SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE as isize,
283    IllegalUserName = raw::SSH_DISCONNECT_ILLEGAL_USER_NAME as isize,
284}
285
286#[allow(missing_docs)]
287#[derive(Copy, Clone, Debug)]
288pub enum HostKeyType {
289    Unknown = raw::LIBSSH2_HOSTKEY_TYPE_UNKNOWN as isize,
290    Rsa = raw::LIBSSH2_HOSTKEY_TYPE_RSA as isize,
291    Dss = raw::LIBSSH2_HOSTKEY_TYPE_DSS as isize,
292    Ecdsa256 = raw::LIBSSH2_HOSTKEY_TYPE_ECDSA_256 as isize,
293    Ecdsa384 = raw::LIBSSH2_HOSTKEY_TYPE_ECDSA_384 as isize,
294    Ecdsa521 = raw::LIBSSH2_HOSTKEY_TYPE_ECDSA_521 as isize,
295    Ed25519 = raw::LIBSSH2_HOSTKEY_TYPE_ED25519 as isize,
296}
297
298#[allow(missing_docs)]
299#[derive(Copy, Clone)]
300pub enum MethodType {
301    Kex = raw::LIBSSH2_METHOD_KEX as isize,
302    HostKey = raw::LIBSSH2_METHOD_HOSTKEY as isize,
303    CryptCs = raw::LIBSSH2_METHOD_CRYPT_CS as isize,
304    CryptSc = raw::LIBSSH2_METHOD_CRYPT_SC as isize,
305    MacCs = raw::LIBSSH2_METHOD_MAC_CS as isize,
306    MacSc = raw::LIBSSH2_METHOD_MAC_SC as isize,
307    CompCs = raw::LIBSSH2_METHOD_COMP_CS as isize,
308    CompSc = raw::LIBSSH2_METHOD_COMP_SC as isize,
309    LangCs = raw::LIBSSH2_METHOD_LANG_CS as isize,
310    LangSc = raw::LIBSSH2_METHOD_LANG_SC as isize,
311    SignAlgo = raw::LIBSSH2_METHOD_SIGN_ALGO as isize,
312}
313
314/// When passed to `Channel::flush_stream`, flushes all extended data
315/// substreams.
316pub static FLUSH_EXTENDED_DATA: i32 = -1;
317/// When passed to `Channel::flush_stream`, flushes all substream.
318pub static FLUSH_ALL: i32 = -2;
319/// Stream ID of the stderr channel for stream-related methods on `Channel`
320pub static EXTENDED_DATA_STDERR: i32 = 1;
321
322#[allow(missing_docs)]
323#[derive(Copy, Clone, Debug)]
324pub enum HashType {
325    Md5 = raw::LIBSSH2_HOSTKEY_HASH_MD5 as isize,
326    Sha1 = raw::LIBSSH2_HOSTKEY_HASH_SHA1 as isize,
327    Sha256 = raw::LIBSSH2_HOSTKEY_HASH_SHA256 as isize,
328}
329
330#[allow(missing_docs)]
331#[derive(Copy, Clone, Debug)]
332pub enum KnownHostFileKind {
333    OpenSSH = raw::LIBSSH2_KNOWNHOST_FILE_OPENSSH as isize,
334}
335
336/// Possible results of a call to `KnownHosts::check`
337#[derive(Copy, Clone, Debug)]
338pub enum CheckResult {
339    /// Hosts and keys match
340    Match = raw::LIBSSH2_KNOWNHOST_CHECK_MATCH as isize,
341    /// Host was found, but the keys didn't match!
342    Mismatch = raw::LIBSSH2_KNOWNHOST_CHECK_MISMATCH as isize,
343    /// No host match was found
344    NotFound = raw::LIBSSH2_KNOWNHOST_CHECK_NOTFOUND as isize,
345    /// Something prevented the check to be made
346    Failure = raw::LIBSSH2_KNOWNHOST_CHECK_FAILURE as isize,
347}
348
349#[allow(missing_docs)]
350#[derive(Copy, Clone, Debug)]
351pub enum KnownHostKeyFormat {
352    Unknown = raw::LIBSSH2_KNOWNHOST_KEY_UNKNOWN as isize,
353    Rsa1 = raw::LIBSSH2_KNOWNHOST_KEY_RSA1 as isize,
354    SshRsa = raw::LIBSSH2_KNOWNHOST_KEY_SSHRSA as isize,
355    SshDss = raw::LIBSSH2_KNOWNHOST_KEY_SSHDSS as isize,
356    Ecdsa256 = raw::LIBSSH2_KNOWNHOST_KEY_ECDSA_256 as isize,
357    Ecdsa384 = raw::LIBSSH2_KNOWNHOST_KEY_ECDSA_384 as isize,
358    Ecdsa521 = raw::LIBSSH2_KNOWNHOST_KEY_ECDSA_521 as isize,
359    Ed25519 = raw::LIBSSH2_KNOWNHOST_KEY_ED25519 as isize,
360}
361
362impl From<HostKeyType> for KnownHostKeyFormat {
363    fn from(host_type: HostKeyType) -> KnownHostKeyFormat {
364        match host_type {
365            HostKeyType::Unknown => KnownHostKeyFormat::Unknown,
366            HostKeyType::Rsa => KnownHostKeyFormat::SshRsa,
367            HostKeyType::Dss => KnownHostKeyFormat::SshDss,
368            HostKeyType::Ecdsa256 => KnownHostKeyFormat::Ecdsa256,
369            HostKeyType::Ecdsa384 => KnownHostKeyFormat::Ecdsa384,
370            HostKeyType::Ecdsa521 => KnownHostKeyFormat::Ecdsa521,
371            HostKeyType::Ed25519 => KnownHostKeyFormat::Ed25519,
372        }
373    }
374}
375
376/// How to handle extended data streams, such as stderr
377#[derive(Copy, Clone, Debug)]
378pub enum ExtendedData {
379    /// Queue extended data for eventual reading
380    Normal = raw::LIBSSH2_CHANNEL_EXTENDED_DATA_NORMAL as isize,
381    /// Treat extended data and ordinary data the same. Merge all substreams such that calls to
382    /// read will pull from all substreams on a first-in/first-out basis.
383    Merge = raw::LIBSSH2_CHANNEL_EXTENDED_DATA_MERGE as isize,
384    /// Discard all extended data as it arrives.
385    Ignore = raw::LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE as isize,
386}
387
388/// The modes described in <https://tools.ietf.org/html/rfc4250#section-4.5.2>
389#[allow(non_camel_case_types)]
390#[derive(Debug, Clone, Copy, Eq, PartialEq)]
391pub enum PtyModeOpcode {
392    /// Indicates end of options.
393    TTY_OP_END = 0,
394    /// Interrupt character; 255 if none.  Similarly for the other characters.  Not all of these characters are supported on all systems.
395    VINTR = 1,
396    /// The quit character (sends SIGQUIT signal on POSIX systems).
397    VQUIT = 2,
398    /// Erase the character to left of the cursor.
399    VERASE = 3,
400    /// Kill the current input line.
401    VKILL = 4,
402    /// End-of-file character (sends EOF from the terminal).
403    VEOF = 5,
404    /// End-of-line character in addition to carriage return and/or linefeed.
405    VEOL = 6,
406    /// Additional end-of-line character.
407    VEOL2 = 7,
408    /// Continues paused output (normally control-Q).
409    VSTART = 8,
410    /// Pauses output (normally control-S).
411    VSTOP = 9,
412    /// Suspends the current program.
413    VSUSP = 10,
414    /// Another suspend character.
415    VDSUSP = 11,
416    /// Reprints the current input line.
417    VREPRINT = 12,
418    /// Erases a word left of cursor.
419    VWERASE = 13,
420    /// Enter the next character typed literally, even if it is a special character
421    VLNEXT = 14,
422    /// Character to flush output.
423    VFLUSH = 15,
424    /// Switch to a different shell layer.
425    VSWTCH = 16,
426    /// Prints system status line (load, command, pid, etc).
427    VSTATUS = 17,
428    /// Toggles the flushing of terminal output.
429    VDISCARD = 18,
430    /// The ignore parity flag.  The parameter SHOULD be 0 if this flag is FALSE, and 1 if it is TRUE.
431    IGNPAR = 30,
432    /// Mark parity and framing errors.
433    PARMRK = 31,
434    /// Enable checking of parity errors.
435    INPCK = 32,
436    /// Strip 8th bit off characters.
437    ISTRIP = 33,
438    /// Map NL into CR on input.
439    INLCR = 34,
440    /// Ignore CR on input.
441    IGNCR = 35,
442    /// Map CR to NL on input.
443    ICRNL = 36,
444    /// Translate uppercase characters to lowercase.
445    IUCLC = 37,
446    /// Enable output flow control.
447    IXON = 38,
448    /// Any char will restart after stop.
449    IXANY = 39,
450    /// Enable input flow control.
451    IXOFF = 49,
452    /// Ring bell on input queue full.
453    IMAXBEL = 41,
454    /// Enable signals INTR, QUIT, [D]SUSP.
455    ISIG = 50,
456    /// Canonicalize input lines.
457    ICANON = 51,
458
459    /// Enable input and output of uppercase characters by preceding their lowercase equivalents with "\".
460    XCASE = 52,
461    /// Enable echoing.
462    ECHO = 53,
463    /// Visually erase chars.
464    ECHOE = 54,
465    /// Kill character discards current line.
466    ECHOK = 55,
467    /// Echo NL even if ECHO is off.
468    ECHONL = 56,
469    /// Don't flush after interrupt.
470    NOFLSH = 57,
471    /// Stop background jobs from output.
472    TOSTOP = 58,
473    /// Enable extensions.
474    IEXTEN = 59,
475    /// Echo control characters as ^(Char).
476    ECHOCTL = 60,
477    /// Visual erase for line kill.
478    ECHOKE = 61,
479    /// Retype pending input.
480    PENDIN = 62,
481    /// Enable output processing.
482    OPOST = 70,
483    /// Convert lowercase to uppercase.
484    OLCUC = 71,
485    /// Map NL to CR-NL.
486    ONLCR = 72,
487    /// Translate carriage return to newline (output).
488    OCRNL = 73,
489    /// Translate newline to carriage return-newline (output).
490    ONOCR = 74,
491    /// Newline performs a carriage return (output).
492    ONLRET = 75,
493    /// 7 bit mode.
494    CS7 = 90,
495    /// 8 bit mode.
496    CS8 = 91,
497    /// Parity enable.
498    PARENB = 92,
499    /// Odd parity, else even.
500    PARODD = 93,
501
502    /// Specifies the input baud rate in bits per second.
503    TTY_OP_ISPEED = 128,
504    /// Specifies the output baud rate in bits per second.
505    TTY_OP_OSPEED = 129,
506}
507
508/// An opcode for setting a Pty terminal mode
509#[derive(Debug, Clone, Copy, Eq, PartialEq)]
510pub enum ExtensiblePtyModeOpcode {
511    /// Use one of the modes specified by RFC 4250
512    Mode(PtyModeOpcode),
513    /// Use a mode not reflected by RFC 4250
514    Extended(u8),
515}
516
517impl From<PtyModeOpcode> for ExtensiblePtyModeOpcode {
518    fn from(op: PtyModeOpcode) -> ExtensiblePtyModeOpcode {
519        ExtensiblePtyModeOpcode::Mode(op)
520    }
521}
522
523impl From<u8> for ExtensiblePtyModeOpcode {
524    fn from(op: u8) -> ExtensiblePtyModeOpcode {
525        ExtensiblePtyModeOpcode::Extended(op)
526    }
527}
528
529impl ExtensiblePtyModeOpcode {
530    fn as_opcode(&self) -> u8 {
531        match self {
532            ExtensiblePtyModeOpcode::Mode(m) => *m as u8,
533            ExtensiblePtyModeOpcode::Extended(op) => *op,
534        }
535    }
536}
537
538/// Encodes modes for Pty allocation requests.
539/// The modes documented in <https://tools.ietf.org/html/rfc4250#section-4.5>
540/// are supported.
541#[derive(Debug, Clone)]
542pub struct PtyModes {
543    data: Vec<u8>,
544}
545
546impl PtyModes {
547    /// Construct a PtyModes instance so that you can specify values for
548    /// various modes
549    pub fn new() -> Self {
550        Self { data: vec![] }
551    }
552
553    /// Set a mode to an arbitrary u32 value
554    pub fn set_u32<O: Into<ExtensiblePtyModeOpcode>>(&mut self, option: O, value: u32) {
555        let data = [
556            option.into().as_opcode(),
557            ((value >> 24) & 0xff) as u8,
558            ((value >> 16) & 0xff) as u8,
559            ((value >> 8) & 0xff) as u8,
560            (value & 0xff) as u8,
561        ];
562        self.data.extend_from_slice(&data);
563    }
564
565    /// Set a mode to a boolean value
566    pub fn set_boolean<O: Into<ExtensiblePtyModeOpcode>>(&mut self, option: O, value: bool) {
567        self.set_u32(option, if value { 1 } else { 0 })
568    }
569
570    /// Set a mode to a character value.
571    /// If the character is None it is set to 255 to indicate that it
572    /// is disabled.
573    /// While this interface and the protocol accept unicode characters
574    /// of up to 32 bits in width, these options likely only work for
575    /// characters in the 7-bit ascii range.
576    pub fn set_character<O: Into<ExtensiblePtyModeOpcode>>(&mut self, option: O, c: Option<char>) {
577        self.set_u32(option, c.map(|c| c as u32).unwrap_or(255))
578    }
579
580    /// Finish accumulating modes and return the encoded
581    /// byte stream suitable for use in the ssh2 protocol
582    pub fn finish(mut self) -> Vec<u8> {
583        self.data.push(PtyModeOpcode::TTY_OP_END as u8);
584        self.data
585    }
586}