1 // Copyright 2014-2017 The Rpassword Developers
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #[cfg(unix)]
16 extern crate libc;
17
18 #[cfg(windows)]
19 extern crate winapi;
20
21 use std::io::Write;
22
23 mod zero_on_drop;
24 use zero_on_drop::ZeroOnDrop;
25
26 /// Removes the \n from the read line
fixes_newline(password: &mut ZeroOnDrop)27 fn fixes_newline(password: &mut ZeroOnDrop) {
28 // We may not have a newline, e.g. if user sent CTRL-D or if
29 // this is not a TTY.
30
31 if password.ends_with('\n') {
32 // Remove the \n from the line if present
33 password.pop();
34
35 // Remove the \r from the line if present
36 if password.ends_with('\r') {
37 password.pop();
38 }
39 }
40 }
41
42 #[cfg(unix)]
43 mod unix {
44 use libc::{c_int, isatty, tcsetattr, termios, ECHO, ECHONL, STDIN_FILENO, TCSANOW};
45 use std::io::{self, BufRead, Write};
46 use std::mem;
47 use std::os::unix::io::AsRawFd;
48
49 /// Turns a C function return into an IO Result
io_result(ret: c_int) -> ::std::io::Result<()>50 fn io_result(ret: c_int) -> ::std::io::Result<()> {
51 match ret {
52 0 => Ok(()),
53 _ => Err(::std::io::Error::last_os_error()),
54 }
55 }
56
safe_tcgetattr(fd: c_int) -> ::std::io::Result<termios>57 fn safe_tcgetattr(fd: c_int) -> ::std::io::Result<termios> {
58 let mut term = mem::MaybeUninit::<::unix::termios>::uninit();
59 io_result(unsafe { ::libc::tcgetattr(fd, term.as_mut_ptr()) })?;
60 Ok(unsafe { term.assume_init() })
61 }
62
63 /// Reads a password from stdin
read_password_from_stdin(open_tty: bool) -> ::std::io::Result<String>64 pub fn read_password_from_stdin(open_tty: bool) -> ::std::io::Result<String> {
65 let mut password = super::ZeroOnDrop::new();
66
67 enum Source {
68 Tty(io::BufReader<::std::fs::File>),
69 Stdin(io::Stdin),
70 }
71
72 let (tty_fd, mut source) = if open_tty {
73 let tty = ::std::fs::File::open("/dev/tty")?;
74 (tty.as_raw_fd(), Source::Tty(io::BufReader::new(tty)))
75 } else {
76 (STDIN_FILENO, Source::Stdin(io::stdin()))
77 };
78
79 let input_is_tty = unsafe { isatty(tty_fd) } == 1;
80
81 // When we ask for a password in a terminal, we'll want to hide the password as it is
82 // typed by the user
83 if input_is_tty {
84 // Make two copies of the terminal settings. The first one will be modified
85 // and the second one will act as a backup for when we want to set the
86 // terminal back to its original state.
87 let mut term = safe_tcgetattr(tty_fd)?;
88 let term_orig = safe_tcgetattr(tty_fd)?;
89
90 // Hide the password. This is what makes this function useful.
91 term.c_lflag &= !ECHO;
92
93 // But don't hide the NL character when the user hits ENTER.
94 term.c_lflag |= ECHONL;
95
96 // Save the settings for now.
97 io_result(unsafe { tcsetattr(tty_fd, TCSANOW, &term) })?;
98
99 // Read the password.
100 let input = match source {
101 Source::Tty(ref mut tty) => tty.read_line(&mut password),
102 Source::Stdin(ref mut stdin) => stdin.read_line(&mut password),
103 };
104
105 // Reset the terminal.
106 io_result(unsafe { tcsetattr(tty_fd, TCSANOW, &term_orig) })?;
107
108 // Return if we have an error
109 input?;
110 } else {
111 // If we don't have a TTY, the input was piped so we bypass
112 // terminal hiding code
113 match source {
114 Source::Tty(mut tty) => tty.read_line(&mut password)?,
115 Source::Stdin(stdin) => stdin.read_line(&mut password)?,
116 };
117 }
118
119 super::fixes_newline(&mut password);
120
121 Ok(password.into_inner())
122 }
123
124 /// Displays a prompt on the terminal
display_on_tty(prompt: &str) -> ::std::io::Result<()>125 pub fn display_on_tty(prompt: &str) -> ::std::io::Result<()> {
126 let mut stream = ::std::fs::OpenOptions::new().write(true).open("/dev/tty")?;
127 write!(stream, "{}", prompt)?;
128 stream.flush()
129 }
130 }
131
132 #[cfg(windows)]
133 mod windows {
134 use std::io::{self, Write};
135 use std::os::windows::io::{AsRawHandle, FromRawHandle};
136 use std::ptr;
137 use winapi::shared::minwindef::LPDWORD;
138 use winapi::um::consoleapi::{GetConsoleMode, SetConsoleMode};
139 use winapi::um::fileapi::{CreateFileA, GetFileType, OPEN_EXISTING};
140 use winapi::um::handleapi::INVALID_HANDLE_VALUE;
141 use winapi::um::processenv::GetStdHandle;
142 use winapi::um::winbase::{FILE_TYPE_PIPE, STD_INPUT_HANDLE};
143 use winapi::um::wincon::{ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT};
144 use winapi::um::winnt::{FILE_SHARE_READ, FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE};
145
146 /// Reads a password from stdin
read_password_from_stdin(open_tty: bool) -> io::Result<String>147 pub fn read_password_from_stdin(open_tty: bool) -> io::Result<String> {
148 let mut password = super::ZeroOnDrop::new();
149
150 // Get the stdin handle
151 let handle = if open_tty {
152 unsafe {
153 CreateFileA(
154 b"CONIN$\x00".as_ptr() as *const i8,
155 GENERIC_READ | GENERIC_WRITE,
156 FILE_SHARE_READ | FILE_SHARE_WRITE,
157 ptr::null_mut(),
158 OPEN_EXISTING,
159 0,
160 ptr::null_mut(),
161 )
162 }
163 } else {
164 unsafe { GetStdHandle(STD_INPUT_HANDLE) }
165 };
166 if handle == INVALID_HANDLE_VALUE {
167 return Err(::std::io::Error::last_os_error());
168 }
169
170 let mut mode = 0;
171
172 // Console mode does not apply when stdin is piped
173 let handle_type = unsafe { GetFileType(handle) };
174 if handle_type != FILE_TYPE_PIPE {
175 // Get the old mode so we can reset back to it when we are done
176 if unsafe { GetConsoleMode(handle, &mut mode as LPDWORD) } == 0 {
177 return Err(::std::io::Error::last_os_error());
178 }
179
180 // We want to be able to read line by line, and we still want backspace to work
181 let new_mode_flags = ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT;
182 if unsafe { SetConsoleMode(handle, new_mode_flags) } == 0 {
183 return Err(::std::io::Error::last_os_error());
184 }
185 }
186
187 // Read the password.
188 let source = io::stdin();
189 let input = source.read_line(&mut password);
190 let handle = source.as_raw_handle();
191
192 // Check the response.
193 input?;
194
195 if handle_type != FILE_TYPE_PIPE {
196 // Set the the mode back to normal
197 if unsafe { SetConsoleMode(handle, mode) } == 0 {
198 return Err(::std::io::Error::last_os_error());
199 }
200 }
201
202 super::fixes_newline(&mut password);
203
204 // Newline for windows which otherwise prints on the same line.
205 println!();
206
207 Ok(password.into_inner())
208 }
209
210 /// Displays a prompt on the terminal
display_on_tty(prompt: &str) -> ::std::io::Result<()>211 pub fn display_on_tty(prompt: &str) -> ::std::io::Result<()> {
212 let handle = unsafe {
213 CreateFileA(
214 b"CONOUT$\x00".as_ptr() as *const i8,
215 GENERIC_READ | GENERIC_WRITE,
216 FILE_SHARE_READ | FILE_SHARE_WRITE,
217 ::std::ptr::null_mut(),
218 OPEN_EXISTING,
219 0,
220 ::std::ptr::null_mut(),
221 )
222 };
223 if handle == INVALID_HANDLE_VALUE {
224 return Err(::std::io::Error::last_os_error());
225 }
226
227 let mut stream = unsafe { ::std::fs::File::from_raw_handle(handle) };
228
229 write!(stream, "{}", prompt)?;
230 stream.flush()
231 }
232 }
233
234 #[cfg(unix)]
235 use unix::{display_on_tty, read_password_from_stdin};
236 #[cfg(windows)]
237 use windows::{display_on_tty, read_password_from_stdin};
238
239 /// Reads a password from anything that implements BufRead
240 mod mock {
241 use super::*;
242
243 /// Reads a password from STDIN
read_password() -> ::std::io::Result<String>244 pub fn read_password() -> ::std::io::Result<String> {
245 read_password_with_reader(None::<&mut ::std::io::Empty>)
246 }
247
248 /// Reads a password from anything that implements BufRead
read_password_with_reader<T>(source: Option<&mut T>) -> ::std::io::Result<String> where T: ::std::io::BufRead,249 pub fn read_password_with_reader<T>(source: Option<&mut T>) -> ::std::io::Result<String>
250 where
251 T: ::std::io::BufRead,
252 {
253 match source {
254 Some(reader) => {
255 let mut password = ZeroOnDrop::new();
256 if let Err(err) = reader.read_line(&mut password) {
257 Err(err)
258 } else {
259 fixes_newline(&mut password);
260 Ok(password.into_inner())
261 }
262 }
263 None => read_password_from_stdin(false),
264 }
265 }
266
267 #[cfg(test)]
268 mod tests {
269 use std::io::Cursor;
270
mock_input_crlf() -> Cursor<&'static [u8]>271 fn mock_input_crlf() -> Cursor<&'static [u8]> {
272 Cursor::new(&b"A mocked response.\r\nAnother mocked response.\r\n"[..])
273 }
274
mock_input_lf() -> Cursor<&'static [u8]>275 fn mock_input_lf() -> Cursor<&'static [u8]> {
276 Cursor::new(&b"A mocked response.\nAnother mocked response.\n"[..])
277 }
278
279 #[test]
can_read_from_redirected_input_many_times()280 fn can_read_from_redirected_input_many_times() {
281 let mut reader_crlf = mock_input_crlf();
282
283 let response = ::read_password_with_reader(Some(&mut reader_crlf)).unwrap();
284 assert_eq!(response, "A mocked response.");
285 let response = ::read_password_with_reader(Some(&mut reader_crlf)).unwrap();
286 assert_eq!(response, "Another mocked response.");
287
288 let mut reader_lf = mock_input_lf();
289 let response = ::read_password_with_reader(Some(&mut reader_lf)).unwrap();
290 assert_eq!(response, "A mocked response.");
291 let response = ::read_password_with_reader(Some(&mut reader_lf)).unwrap();
292 assert_eq!(response, "Another mocked response.");
293 }
294 }
295 }
296
297 pub use mock::{read_password, read_password_with_reader};
298
299 /// Reads a password from the terminal
read_password_from_tty(prompt: Option<&str>) -> ::std::io::Result<String>300 pub fn read_password_from_tty(prompt: Option<&str>) -> ::std::io::Result<String> {
301 if let Some(prompt) = prompt {
302 display_on_tty(prompt)?;
303 }
304 read_password_from_stdin(true)
305 }
306
307 /// Prompts for a password on STDOUT and reads it from STDIN
prompt_password_stdout(prompt: &str) -> std::io::Result<String>308 pub fn prompt_password_stdout(prompt: &str) -> std::io::Result<String> {
309 let mut stdout = std::io::stdout();
310
311 write!(stdout, "{}", prompt)?;
312 stdout.flush()?;
313 read_password()
314 }
315
316 /// Prompts for a password on STDERR and reads it from STDIN
prompt_password_stderr(prompt: &str) -> std::io::Result<String>317 pub fn prompt_password_stderr(prompt: &str) -> std::io::Result<String> {
318 let mut stderr = std::io::stderr();
319
320 write!(stderr, "{}", prompt)?;
321 stderr.flush()?;
322 read_password()
323 }
324