Skip to content

Commit a167ef0

Browse files
committed
Implement askpass support
1 parent 24101bb commit a167ef0

File tree

5 files changed

+108
-2
lines changed

5 files changed

+108
-2
lines changed

src/pam/askpass.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use std::os::fd::{FromRawFd, OwnedFd};
2+
use std::os::unix::process::CommandExt;
3+
use std::path::Path;
4+
use std::process::Command;
5+
use std::{io, process};
6+
7+
use libc::O_CLOEXEC;
8+
9+
use crate::cutils::cerr;
10+
use crate::system::interface::ProcessId;
11+
use crate::system::{fork, mark_fds_as_cloexec, ForkResult};
12+
13+
pub(super) fn spawn_askpass(program: &Path, prompt: &str) -> io::Result<(ProcessId, OwnedFd)> {
14+
// Create socket
15+
let mut pipes = [-1, -1];
16+
// SAFETY: A valid pointer to a mutable array of 2 fds is passed in.
17+
unsafe {
18+
cerr(libc::pipe2(pipes.as_mut_ptr(), O_CLOEXEC))?;
19+
}
20+
// SAFETY: pipe2 created two owned pipe fds
21+
let (pipe_read, pipe_write) = unsafe {
22+
(
23+
OwnedFd::from_raw_fd(pipes[0]),
24+
OwnedFd::from_raw_fd(pipes[1]),
25+
)
26+
};
27+
28+
// Spawn child
29+
// SAFETY: There should be no other threads at this point.
30+
let ForkResult::Parent(command_pid) = unsafe { fork() }.unwrap() else {
31+
drop(pipe_read);
32+
handle_child(program, prompt, pipe_write)
33+
};
34+
drop(pipe_write);
35+
36+
Ok((command_pid, pipe_read))
37+
}
38+
39+
fn handle_child(program: &Path, prompt: &str, stdout: OwnedFd) -> ! {
40+
// Drop root privileges.
41+
// SAFETY: setuid does not change any memory and only affects OS state.
42+
unsafe {
43+
libc::setuid(libc::getuid());
44+
}
45+
46+
if let Err(e) = mark_fds_as_cloexec() {
47+
eprintln_ignore_io_error!("Failed to mark fds as CLOEXEC: {e}");
48+
process::exit(1);
49+
};
50+
51+
// Exec askpass program
52+
let error = Command::new(program).arg(prompt).stdout(stdout).exec();
53+
eprintln_ignore_io_error!(
54+
"Failed to run askpass program {}: {error}",
55+
program.display()
56+
);
57+
process::exit(1);
58+
}

src/pam/converse.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,9 @@ impl Drop for SignalGuard {
129129

130130
impl CLIConverser {
131131
fn open(&self) -> PamResult<(Terminal<'_>, SignalGuard)> {
132-
let term = if self.use_stdin {
132+
let term = if self.use_askpass {
133+
Terminal::open_askpass()?
134+
} else if self.use_stdin {
133135
Terminal::open_stdie()?
134136
} else {
135137
Terminal::open_tty()?

src/pam/error.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::ffi::{c_int, NulError};
22
use std::fmt;
3+
use std::path::PathBuf;
34
use std::str::Utf8Error;
45

56
use crate::cutils::string_from_ptr;
@@ -182,6 +183,8 @@ pub enum PamError {
182183
IncorrectPasswordAttempt,
183184
TimedOut,
184185
InvalidUser(String, String),
186+
NoAskpassProgram,
187+
InvalidAskpassProgram(PathBuf),
185188
}
186189

187190
impl From<std::io::Error> for PamError {
@@ -238,6 +241,14 @@ impl fmt::Display for PamError {
238241
"Sorry, user {username} is not allowed to authenticate as {other_user}.",
239242
)
240243
}
244+
PamError::NoAskpassProgram => write!(f, "No askpass program specified in SUDO_ASKPASS"),
245+
PamError::InvalidAskpassProgram(program) => {
246+
write!(
247+
f,
248+
"Askpass program `{}` is not absolute path",
249+
program.display()
250+
)
251+
}
241252
}
242253
}
243254
}

src/pam/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use error::pam_err;
1414
pub use error::{PamError, PamErrorType, PamResult};
1515
use sys::*;
1616

17+
mod askpass;
1718
mod converse;
1819
mod error;
1920
mod rpassword;

src/pam/rpassword.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616
use std::ffi::c_void;
1717
use std::io::{self, ErrorKind, Read};
1818
use std::os::fd::{AsFd, AsRawFd, BorrowedFd};
19+
use std::path::PathBuf;
1920
use std::time::{Duration, Instant};
2021
use std::{fs, mem};
2122

2223
use libc::{tcsetattr, termios, ECHO, ECHONL, ICANON, TCSANOW, VEOF, VERASE, VKILL};
2324

2425
use crate::cutils::{cerr, safe_isatty};
25-
use crate::pam::{PamError, PamResult};
26+
use crate::pam::{askpass, PamError, PamResult};
27+
use crate::system::wait::{Wait, WaitError, WaitOptions};
2628

2729
use super::securemem::PamBuffer;
2830

@@ -253,6 +255,7 @@ impl io::Read for TimeoutRead<'_> {
253255
pub enum Terminal<'a> {
254256
Tty(fs::File),
255257
StdIE(io::StdinLock<'a>, io::StderrLock<'a>),
258+
Askpass(PathBuf, io::Sink),
256259
}
257260

258261
impl Terminal<'_> {
@@ -274,6 +277,19 @@ impl Terminal<'_> {
274277
Ok(Terminal::StdIE(io::stdin().lock(), io::stderr().lock()))
275278
}
276279

280+
pub fn open_askpass() -> PamResult<Self> {
281+
let Some(program) = std::env::var_os("SUDO_ASKPASS") else {
282+
return Err(PamError::NoAskpassProgram);
283+
};
284+
let program = PathBuf::from(program);
285+
286+
if program.is_absolute() {
287+
Ok(Terminal::Askpass(program, io::sink()))
288+
} else {
289+
Err(PamError::InvalidAskpassProgram(program))
290+
}
291+
}
292+
277293
/// Reads input with TTY echo and visual feedback set according to the `hidden` parameter.
278294
pub(super) fn read_input(
279295
&mut self,
@@ -310,6 +326,23 @@ impl Terminal<'_> {
310326
let mut reader = TimeoutRead::new(file.as_fd(), timeout);
311327
read_unbuffered(&mut reader, &mut &*file, &hide_input)
312328
}
329+
Terminal::Askpass(program, sink) => {
330+
let (command_pid, askpass_stdout) = askpass::spawn_askpass(program, prompt)?;
331+
332+
let mut reader = TimeoutRead::new(askpass_stdout.as_fd(), timeout);
333+
let password = read_unbuffered(&mut reader, sink, &Hidden::No)?;
334+
335+
loop {
336+
match command_pid.wait(WaitOptions::new()) {
337+
Ok(_) => break,
338+
Err(WaitError::Io(err)) if err.kind() == io::ErrorKind::Interrupted => {}
339+
Err(WaitError::Io(err)) => return Err(PamError::IoError(err)),
340+
Err(WaitError::NotReady) => unreachable!(),
341+
}
342+
}
343+
344+
Ok(password)
345+
}
313346
}
314347
}
315348

@@ -329,6 +362,7 @@ impl Terminal<'_> {
329362
match self {
330363
Terminal::StdIE(_, x) => x,
331364
Terminal::Tty(x) => x,
365+
Terminal::Askpass(_, x) => x,
332366
}
333367
}
334368
}

0 commit comments

Comments
 (0)