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}