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