Works?
This commit is contained in:
9
Cargo.lock
generated
9
Cargo.lock
generated
@@ -182,14 +182,6 @@ dependencies = [
|
||||
"serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "greetctl"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"greet_proto 0.2.0",
|
||||
"rpassword 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "greetd"
|
||||
version = "0.1.1"
|
||||
@@ -474,6 +466,7 @@ dependencies = [
|
||||
"mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pin-project-lite 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-macros 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
@@ -5,4 +5,4 @@ panic = "abort"
|
||||
panic = "abort"
|
||||
|
||||
[workspace]
|
||||
members = ["greet_proto", "greetd", "greetctl", "agreety"]
|
||||
members = ["greet_proto", "greetd", "agreety"]
|
||||
|
@@ -8,7 +8,7 @@ use ini::Ini;
|
||||
use nix::sys::utsname::uname;
|
||||
use rpassword::prompt_password_stderr;
|
||||
|
||||
use greet_proto::{Header, Request, Response};
|
||||
use greet_proto::{Header, Question, QuestionStyle, Request, Response};
|
||||
|
||||
fn prompt_stderr(prompt: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let stdin = io::stdin();
|
||||
@@ -50,16 +50,14 @@ fn get_issue() -> Result<String, Box<dyn std::error::Error>> {
|
||||
}
|
||||
|
||||
fn login(node: &str, cmd: Option<&str>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let username = prompt_stderr(&format!("{} login: ", node)).unwrap();
|
||||
let password = prompt_password_stderr("Password: ").unwrap();
|
||||
let username = prompt_stderr(&format!("{} login: ", node))?;
|
||||
let command = match cmd {
|
||||
Some(cmd) => cmd.to_string(),
|
||||
None => prompt_stderr("Command: ").unwrap(),
|
||||
None => prompt_stderr("Command: ")?,
|
||||
};
|
||||
|
||||
let request = Request::Login {
|
||||
let request = Request::Initiate {
|
||||
username,
|
||||
password,
|
||||
env: vec![
|
||||
format!("XDG_SESSION_DESKTOP={}", &command),
|
||||
format!("XDG_CURRENT_DESKTOP={}", &command),
|
||||
@@ -86,11 +84,83 @@ fn login(node: &str, cmd: Option<&str>) -> Result<(), Box<dyn std::error::Error>
|
||||
let resp = Response::from_slice(&resp_buf)?;
|
||||
|
||||
match resp {
|
||||
Response::Success => Ok(()),
|
||||
Response::Failure(err) => {
|
||||
Err(std::io::Error::new(io::ErrorKind::Other, format!("login error: {:?}", err)).into())
|
||||
Response::Success => (),
|
||||
Response::Failure(err) => return Err(format!("login error: {:?}", err).into()),
|
||||
_ => return Err("unexpected response".into()),
|
||||
}
|
||||
|
||||
loop {
|
||||
let request = Request::GetQuestion;
|
||||
let req = request.to_bytes()?;
|
||||
let header = Header::new(req.len() as u32);
|
||||
stream.write_all(&header.to_bytes()?)?;
|
||||
stream.write_all(&req)?;
|
||||
|
||||
// Read response
|
||||
let mut header_buf = vec![0; Header::len()];
|
||||
stream.read_exact(&mut header_buf)?;
|
||||
let header = Header::from_slice(&header_buf)?;
|
||||
|
||||
let mut resp_buf = vec![0; header.len as usize];
|
||||
stream.read_exact(&mut resp_buf)?;
|
||||
let resp = Response::from_slice(&resp_buf)?;
|
||||
|
||||
let mut starting = false;
|
||||
|
||||
let request = match resp {
|
||||
Response::Question { next_question } => match next_question {
|
||||
Some(Question { msg, style }) => {
|
||||
let answer = match style {
|
||||
QuestionStyle::Visible => prompt_stderr(&msg)?,
|
||||
QuestionStyle::Secret => prompt_password_stderr(&msg)?,
|
||||
QuestionStyle::Info => {
|
||||
eprintln!("info: {}", msg);
|
||||
"".to_string()
|
||||
}
|
||||
QuestionStyle::Error => {
|
||||
eprintln!("error: {}", msg);
|
||||
"".to_string()
|
||||
}
|
||||
};
|
||||
|
||||
Request::Answer {
|
||||
answer: Some(answer),
|
||||
}
|
||||
}
|
||||
None => {
|
||||
starting = true;
|
||||
Request::Start
|
||||
}
|
||||
},
|
||||
Response::Failure(err) => return Err(format!("login error: {:?}", err).into()),
|
||||
_ => return Err("unexpected response".into()),
|
||||
};
|
||||
|
||||
let req = request.to_bytes()?;
|
||||
let header = Header::new(req.len() as u32);
|
||||
stream.write_all(&header.to_bytes()?)?;
|
||||
stream.write_all(&req)?;
|
||||
|
||||
// Read response
|
||||
let mut header_buf = vec![0; Header::len()];
|
||||
stream.read_exact(&mut header_buf)?;
|
||||
let header = Header::from_slice(&header_buf)?;
|
||||
|
||||
let mut resp_buf = vec![0; header.len as usize];
|
||||
stream.read_exact(&mut resp_buf)?;
|
||||
let resp = Response::from_slice(&resp_buf)?;
|
||||
|
||||
match resp {
|
||||
Response::Success => (),
|
||||
Response::Failure(err) => return Err(format!("login error: {:?}", err).into()),
|
||||
_ => return Err("unexpected response".into()),
|
||||
}
|
||||
|
||||
if starting {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@@ -133,9 +203,7 @@ fn main() {
|
||||
Ok(()) => {
|
||||
break;
|
||||
}
|
||||
Err(_) => {
|
||||
eprintln!("");
|
||||
}
|
||||
Err(e) => eprintln!("error: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -65,12 +65,17 @@ pub enum ShutdownAction {
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Request {
|
||||
Login {
|
||||
Initiate {
|
||||
username: String,
|
||||
password: String,
|
||||
cmd: Vec<String>,
|
||||
env: Vec<String>,
|
||||
},
|
||||
GetQuestion,
|
||||
Answer {
|
||||
answer: Option<String>,
|
||||
},
|
||||
Cancel,
|
||||
Start,
|
||||
Shutdown {
|
||||
action: ShutdownAction,
|
||||
},
|
||||
@@ -90,7 +95,19 @@ impl Request {
|
||||
#[serde(tag = "error_type")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Failure {
|
||||
LoginError {
|
||||
InitiateError {
|
||||
description: String,
|
||||
},
|
||||
GetQuestionError {
|
||||
description: String,
|
||||
},
|
||||
AnswerError {
|
||||
description: String,
|
||||
},
|
||||
StartError {
|
||||
description: String,
|
||||
},
|
||||
CancelError {
|
||||
description: String,
|
||||
},
|
||||
ShutdownError {
|
||||
@@ -99,11 +116,28 @@ pub enum Failure {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum QuestionStyle {
|
||||
Visible,
|
||||
Secret,
|
||||
Info,
|
||||
Error,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct Question {
|
||||
pub msg: String,
|
||||
pub style: QuestionStyle,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(tag = "type")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Response {
|
||||
Success,
|
||||
Question { next_question: Option<Question> },
|
||||
Failure(Failure),
|
||||
}
|
||||
|
||||
|
@@ -1,9 +0,0 @@
|
||||
[package]
|
||||
name = "greetctl"
|
||||
version = "0.1.0"
|
||||
authors = ["Kenny Levinsen <kl@kl.wtf>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
greet_proto = { path = "../greet_proto" }
|
||||
rpassword = "4.0"
|
@@ -1,64 +0,0 @@
|
||||
use std::env;
|
||||
use std::io::{self, BufRead, Read, Write};
|
||||
use std::os::unix::net::UnixStream;
|
||||
|
||||
use greet_proto::{Header, Request, Response};
|
||||
|
||||
use rpassword::prompt_password_stderr;
|
||||
|
||||
fn prompt_stderr(prompt: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let stdin = io::stdin();
|
||||
let mut stdin_iter = stdin.lock().lines();
|
||||
eprint!("{}", prompt);
|
||||
Ok(stdin_iter.next().unwrap()?)
|
||||
}
|
||||
|
||||
fn login() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let username = prompt_stderr("Username: ").unwrap();
|
||||
let password = prompt_password_stderr("Password: ").unwrap();
|
||||
let command = prompt_stderr("Command: ").unwrap();
|
||||
|
||||
let request = Request::Login {
|
||||
username,
|
||||
password,
|
||||
cmd: vec![command],
|
||||
env: vec![],
|
||||
};
|
||||
|
||||
// Write request
|
||||
let req = request.to_bytes()?;
|
||||
|
||||
let header = Header::new(req.len() as u32);
|
||||
|
||||
let mut stream = UnixStream::connect(env::var("GREETD_SOCK")?)?;
|
||||
stream.write_all(&header.to_bytes()?)?;
|
||||
stream.write_all(&req)?;
|
||||
|
||||
// Read response
|
||||
let mut header_buf = vec![0; Header::len()];
|
||||
stream.read_exact(&mut header_buf)?;
|
||||
let header = Header::from_slice(&header_buf)?;
|
||||
|
||||
let mut resp_buf = vec![0; header.len as usize];
|
||||
stream.read_exact(&mut resp_buf)?;
|
||||
let resp = Response::from_slice(&resp_buf)?;
|
||||
|
||||
match resp {
|
||||
Response::Success => Ok(()),
|
||||
Response::Failure(err) => {
|
||||
Err(std::io::Error::new(io::ErrorKind::Other, format!("login error: {:?}", err)).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
loop {
|
||||
match login() {
|
||||
Ok(()) => {
|
||||
eprintln!("authentication successful");
|
||||
break;
|
||||
}
|
||||
Err(err) => eprintln!("error: {:?}", err),
|
||||
}
|
||||
}
|
||||
}
|
@@ -15,5 +15,5 @@ serde_json = "1.0"
|
||||
greet_proto = { path = "../greet_proto" }
|
||||
libc = "0.2"
|
||||
futures = "0.3"
|
||||
tokio = { version = "0.2", features = ["net", "stream", "sync", "macros", "signal", "rt-util", "io-util"] }
|
||||
tokio = { version = "0.2", features = ["net", "stream", "sync", "macros", "signal", "rt-util", "io-util", "time"] }
|
||||
tokio-util = { version = "0.2", features = ["codec"] }
|
||||
|
@@ -5,17 +5,19 @@ use std::time::Duration;
|
||||
|
||||
use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus};
|
||||
use nix::unistd::{alarm, execv, fork, ForkResult};
|
||||
use tokio::{sync::RwLock, time::delay_for};
|
||||
|
||||
use crate::scrambler::Scrambler;
|
||||
use crate::session::{Session, SessionChild};
|
||||
use greet_proto::ShutdownAction;
|
||||
use crate::session::{QuestionStyle as SessQuestionStyle, Session, SessionChild, SessionState};
|
||||
use greet_proto::{Question, QuestionStyle, ShutdownAction};
|
||||
|
||||
pub struct ContextInner {
|
||||
current_session: Option<SessionChild>,
|
||||
pending_session: Option<Session>,
|
||||
}
|
||||
|
||||
/// Context keeps track of running sessions and start new ones.
|
||||
pub struct Context<'a> {
|
||||
session: Option<SessionChild>,
|
||||
greeter: Option<SessionChild>,
|
||||
pending_session: Option<Session<'a>>,
|
||||
|
||||
pub struct Context {
|
||||
inner: RwLock<ContextInner>,
|
||||
greeter_bin: String,
|
||||
greeter_user: String,
|
||||
vt: usize,
|
||||
@@ -38,91 +40,146 @@ fn run(cmd: &str) -> Result<(), Box<dyn Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl<'a> Context<'a> {
|
||||
pub fn new(greeter_bin: String, greeter_user: String, vt: usize) -> Context<'a> {
|
||||
impl Context {
|
||||
pub fn new(greeter_bin: String, greeter_user: String, vt: usize) -> Context {
|
||||
Context {
|
||||
session: None,
|
||||
greeter: None,
|
||||
pending_session: None,
|
||||
inner: RwLock::new(ContextInner {
|
||||
current_session: None,
|
||||
pending_session: None,
|
||||
}),
|
||||
greeter_bin,
|
||||
greeter_user,
|
||||
vt,
|
||||
}
|
||||
}
|
||||
|
||||
/// Start a greeter session.
|
||||
pub async fn greet(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
if self.greeter.is_some() {
|
||||
eprintln!("greeter session already active");
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "greeter already active").into());
|
||||
}
|
||||
|
||||
async fn create_greeter(&self) -> Result<SessionChild, Box<dyn Error>> {
|
||||
let mut pending_session = Session::new(
|
||||
"greeter",
|
||||
"user",
|
||||
&self.greeter_user,
|
||||
"",
|
||||
vec![self.greeter_bin.to_string()],
|
||||
vec![],
|
||||
self.vt,
|
||||
)?;
|
||||
let greeter = match pending_session.start().await {
|
||||
Ok(s) => s,
|
||||
Err(e) => return Err(format!("session start failed: {}", e).into()),
|
||||
};
|
||||
self.greeter = Some(greeter);
|
||||
)
|
||||
.await?;
|
||||
match pending_session.get_state().await {
|
||||
Ok(SessionState::Ready) => (),
|
||||
Ok(SessionState::Question(_, _)) => {
|
||||
return Err("session start failed: unexpected question".into())
|
||||
}
|
||||
Err(err) => return Err(format!("session start failed: {}", err).into()),
|
||||
}
|
||||
match pending_session.start().await {
|
||||
Ok(s) => Ok(s),
|
||||
Err(e) => Err(format!("session start failed: {}", e).into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Start a greeter session.
|
||||
pub async fn greet(&self) -> Result<(), Box<dyn Error>> {
|
||||
{
|
||||
let inner = self.inner.read().await;
|
||||
if inner.current_session.is_some() {
|
||||
eprintln!("session already active");
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "session already active").into());
|
||||
}
|
||||
}
|
||||
|
||||
self.inner.write().await.current_session = Some(self.create_greeter().await?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Start a login session.
|
||||
pub fn login(
|
||||
&mut self,
|
||||
pub async fn initiate(
|
||||
&self,
|
||||
username: String,
|
||||
mut password: String,
|
||||
cmd: Vec<String>,
|
||||
provided_env: Vec<String>,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
if self.greeter.is_none() {
|
||||
eprintln!("login request not valid when greeter is not active");
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "greeter not active").into());
|
||||
}
|
||||
if self.session.is_some() {
|
||||
eprintln!("login session already active");
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "session already active").into());
|
||||
{
|
||||
let inner = self.inner.read().await;
|
||||
if inner.current_session.is_none() {
|
||||
eprintln!("login request requires active session");
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "session not active").into());
|
||||
}
|
||||
}
|
||||
|
||||
let pending_session = Session::new(
|
||||
"login",
|
||||
"user",
|
||||
&username,
|
||||
&password,
|
||||
cmd,
|
||||
provided_env,
|
||||
self.vt,
|
||||
)?;
|
||||
password.scramble();
|
||||
self.pending_session = Some(pending_session);
|
||||
|
||||
// We give the greeter 5 seconds to prove itself well-behaved before
|
||||
// we lose patience and shoot it in the back repeatedly. This is all
|
||||
// handled by our alarm handler.
|
||||
alarm::set(5);
|
||||
let pending_session =
|
||||
Session::new("login", "user", &username, cmd, provided_env, self.vt).await?;
|
||||
self.inner.write().await.pending_session = Some(pending_session);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn shutdown(&mut self, action: ShutdownAction) -> Result<(), Box<dyn Error>> {
|
||||
if self.greeter.is_none() || self.session.is_some() {
|
||||
eprintln!("shutdown request not valid when greeter is not active");
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "greeter not active").into());
|
||||
pub async fn cancel(&self) -> Result<(), Box<dyn Error>> {
|
||||
let pending_session = self.inner.write().await.pending_session.take();
|
||||
if let Some(mut s) = pending_session {
|
||||
s.post_answer(None).await?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_question(&self) -> Result<Option<Question>, Box<dyn Error>> {
|
||||
let mut inner = self.inner.write().await;
|
||||
match &mut inner.pending_session {
|
||||
Some(s) => match s.get_state().await? {
|
||||
SessionState::Ready => Ok(None),
|
||||
SessionState::Question(style, string) => Ok(Some(Question {
|
||||
msg: string,
|
||||
style: match style {
|
||||
SessQuestionStyle::Visible => QuestionStyle::Visible,
|
||||
SessQuestionStyle::Secret => QuestionStyle::Secret,
|
||||
SessQuestionStyle::Info => QuestionStyle::Info,
|
||||
SessQuestionStyle::Error => QuestionStyle::Error,
|
||||
},
|
||||
})),
|
||||
},
|
||||
None => Err("no session active".into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn post_answer(&self, answer: Option<String>) -> Result<(), Box<dyn Error>> {
|
||||
let mut inner = self.inner.write().await;
|
||||
match &mut inner.pending_session {
|
||||
Some(s) => s.post_answer(answer).await,
|
||||
None => Err("no session active".into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start(&self) -> Result<(), Box<dyn Error>> {
|
||||
let mut inner = self.inner.write().await;
|
||||
match &mut inner.pending_session {
|
||||
Some(s) => {
|
||||
match s.get_state().await? {
|
||||
SessionState::Ready => {
|
||||
// We give the greeter 5 seconds to prove itself well-behaved before
|
||||
// we lose patience and shoot it in the back repeatedly. This is all
|
||||
// handled by our alarm handler.
|
||||
alarm::set(5);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
_ => Err("session is not ready".into()),
|
||||
}
|
||||
}
|
||||
None => Err("no session active".into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn shutdown(&self, action: ShutdownAction) -> Result<(), Box<dyn Error>> {
|
||||
{
|
||||
let inner = self.inner.read().await;
|
||||
if inner.current_session.is_none() {
|
||||
eprintln!("shutdown request not valid when greeter is not active");
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "greeter not active").into());
|
||||
}
|
||||
}
|
||||
|
||||
let cmd = match action {
|
||||
ShutdownAction::Poweroff => "poweroff",
|
||||
ShutdownAction::Reboot => "reboot",
|
||||
ShutdownAction::Exit => {
|
||||
self.terminate()?;
|
||||
self.terminate().await?;
|
||||
unreachable!("previous call must always fail");
|
||||
}
|
||||
};
|
||||
@@ -131,10 +188,11 @@ impl<'a> Context<'a> {
|
||||
}
|
||||
|
||||
/// Notify the Context of an alarm.
|
||||
pub async fn alarm(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
pub async fn alarm(&self) -> Result<(), Box<dyn Error>> {
|
||||
// Keep trying to terminate the greeter until it gives up.
|
||||
if let Some(mut p) = self.pending_session.take() {
|
||||
if let Some(g) = self.greeter.take() {
|
||||
let mut inner = self.inner.write().await;
|
||||
if let Some(mut p) = inner.pending_session.take() {
|
||||
if let Some(g) = inner.current_session.take() {
|
||||
if p.elapsed() > Duration::from_secs(10) {
|
||||
// We're out of patience.
|
||||
g.kill();
|
||||
@@ -142,18 +200,18 @@ impl<'a> Context<'a> {
|
||||
// Let's try to give it a gentle nudge.
|
||||
g.term();
|
||||
}
|
||||
self.greeter = Some(g);
|
||||
self.pending_session = Some(p);
|
||||
inner.current_session = Some(g);
|
||||
inner.pending_session = Some(p);
|
||||
alarm::set(1);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
drop(inner);
|
||||
let s = match p.start().await {
|
||||
Ok(s) => s,
|
||||
Err(e) => return Err(format!("session start failed: {}", e).into()),
|
||||
};
|
||||
|
||||
self.session = Some(s);
|
||||
let mut inner = self.inner.write().await;
|
||||
inner.current_session = Some(s);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -161,7 +219,7 @@ impl<'a> Context<'a> {
|
||||
|
||||
/// Notify the Context that it needs to check its children for termination.
|
||||
/// This should be called on SIGCHLD.
|
||||
pub async fn check_children(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
pub async fn check_children(&self) -> Result<(), Box<dyn Error>> {
|
||||
loop {
|
||||
match waitpid(None, Some(WaitPidFlag::WNOHANG)) {
|
||||
// No pending exits.
|
||||
@@ -169,55 +227,45 @@ impl<'a> Context<'a> {
|
||||
|
||||
// We got an exit, see if it's something we need to clean up.
|
||||
Ok(WaitStatus::Exited(pid, ..)) | Ok(WaitStatus::Signaled(pid, ..)) => {
|
||||
match &self.session {
|
||||
let mut inner = self.inner.write().await;
|
||||
let was_greeter;
|
||||
let elapsed;
|
||||
match &inner.current_session {
|
||||
Some(session) if session.owns_pid(pid) => {
|
||||
// Session task is dead, so kill the session and
|
||||
// restart the greeter. However, ensure that we are
|
||||
// not spamming th egreeter by waiting at least one
|
||||
// one second since login was requested before
|
||||
// restarting it.
|
||||
if session.elapsed() < Duration::from_secs(1) {
|
||||
std::thread::sleep(std::time::Duration::from_millis(1000));
|
||||
}
|
||||
self.session = None;
|
||||
eprintln!("session exited");
|
||||
if let Err(e) = self.greet().await {
|
||||
return Err(
|
||||
format!("session start failed for greeter: {}", e).into()
|
||||
);
|
||||
}
|
||||
was_greeter = session.get_service() == "greeter";
|
||||
elapsed = session.elapsed();
|
||||
inner.current_session = None;
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
match &self.greeter {
|
||||
Some(greeter) if greeter.owns_pid(pid) => {
|
||||
self.greeter = None;
|
||||
match self.pending_session.take() {
|
||||
Some(mut pending_session) => {
|
||||
eprintln!("starting pending session");
|
||||
// Our greeter finally bit the dust so we can
|
||||
// start our pending session.
|
||||
let s = match pending_session.start().await {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return Err(
|
||||
format!("session start failed: {}", e).into()
|
||||
);
|
||||
}
|
||||
};
|
||||
_ => continue,
|
||||
}
|
||||
|
||||
self.session = Some(s);
|
||||
match inner.pending_session.take() {
|
||||
Some(mut pending_session) => {
|
||||
eprintln!("starting pending session");
|
||||
// Our greeter finally bit the dust so we can
|
||||
// start our pending session.
|
||||
drop(inner);
|
||||
let s = match pending_session.start().await {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return Err(format!("session start failed: {}", e).into());
|
||||
}
|
||||
None => {
|
||||
if self.session.is_none() {
|
||||
// Greeter died on us, let's just die with it.
|
||||
return Err("greeter died with no pending session".into());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
let mut inner = self.inner.write().await;
|
||||
inner.current_session = Some(s);
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
None if !was_greeter => {
|
||||
if elapsed < Duration::from_secs(1) {
|
||||
delay_for(Duration::from_secs(1)).await;
|
||||
}
|
||||
inner.current_session = Some(self.create_greeter().await?);
|
||||
}
|
||||
None => {
|
||||
// Greeter died on us, let's just die with it.
|
||||
return Err("greeter died with no pending session".into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Useless status.
|
||||
@@ -234,12 +282,13 @@ impl<'a> Context<'a> {
|
||||
|
||||
/// Notify the Context that we want to terminate. This should be called on
|
||||
/// SIGTERM.
|
||||
pub fn terminate(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
if let Some(session) = self.session.take() {
|
||||
session.shoo();
|
||||
pub async fn terminate(&self) -> Result<(), Box<dyn Error>> {
|
||||
let mut inner = self.inner.write().await;
|
||||
if let Some(sess) = &inner.current_session {
|
||||
sess.shoo();
|
||||
}
|
||||
if let Some(greeter) = self.greeter.take() {
|
||||
greeter.shoo();
|
||||
if let Some(sess) = &mut inner.pending_session {
|
||||
let _ = sess.post_answer(None).await;
|
||||
}
|
||||
Err("terminating".into())
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ mod scrambler;
|
||||
mod session;
|
||||
mod terminal;
|
||||
|
||||
use std::{cell::RefCell, error::Error, io, rc::Rc};
|
||||
use std::{error::Error, io, rc::Rc};
|
||||
|
||||
use nix::{
|
||||
sys::mman::{mlockall, MlockAllFlags},
|
||||
@@ -31,7 +31,7 @@ fn reset_vt(vt: usize) -> Result<(), Box<dyn Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn client(ctx: Rc<RefCell<Context<'_>>>, mut s: UnixStream) -> Result<(), Box<dyn Error>> {
|
||||
async fn client(ctx: Rc<Context>, mut s: UnixStream) -> Result<(), Box<dyn Error>> {
|
||||
loop {
|
||||
let mut header_bytes = [0; Header::len()];
|
||||
|
||||
@@ -50,18 +50,39 @@ async fn client(ctx: Rc<RefCell<Context<'_>>>, mut s: UnixStream) -> Result<(),
|
||||
body_bytes.scramble();
|
||||
|
||||
let resp = match req {
|
||||
Request::Login {
|
||||
username,
|
||||
password,
|
||||
cmd,
|
||||
env,
|
||||
} => match ctx.borrow_mut().login(username, password, cmd, env) {
|
||||
Request::Initiate { username, cmd, env } => {
|
||||
match ctx.initiate(username, cmd, env).await {
|
||||
Ok(_) => Response::Success,
|
||||
Err(e) => Response::Failure(Failure::InitiateError {
|
||||
description: format!("{}", e),
|
||||
}),
|
||||
}
|
||||
}
|
||||
Request::Start => match ctx.start().await {
|
||||
Ok(_) => Response::Success,
|
||||
Err(e) => Response::Failure(Failure::LoginError {
|
||||
Err(e) => Response::Failure(Failure::StartError {
|
||||
description: format!("{}", e),
|
||||
}),
|
||||
},
|
||||
Request::Shutdown { action } => match ctx.borrow_mut().shutdown(action) {
|
||||
Request::GetQuestion => match ctx.get_question().await {
|
||||
Ok(v) => Response::Question { next_question: v },
|
||||
Err(e) => Response::Failure(Failure::GetQuestionError {
|
||||
description: format!("{}", e),
|
||||
}),
|
||||
},
|
||||
Request::Cancel => match ctx.cancel().await {
|
||||
Ok(_) => Response::Success,
|
||||
Err(e) => Response::Failure(Failure::CancelError {
|
||||
description: format!("{}", e),
|
||||
}),
|
||||
},
|
||||
Request::Answer { answer } => match ctx.post_answer(answer).await {
|
||||
Ok(_) => Response::Success,
|
||||
Err(e) => Response::Failure(Failure::AnswerError {
|
||||
description: format!("{}", e),
|
||||
}),
|
||||
},
|
||||
Request::Shutdown { action } => match ctx.shutdown(action).await {
|
||||
Ok(_) => Response::Success,
|
||||
Err(e) => Response::Failure(Failure::ShutdownError {
|
||||
action,
|
||||
@@ -79,6 +100,7 @@ async fn client(ctx: Rc<RefCell<Context<'_>>>, mut s: UnixStream) -> Result<(),
|
||||
.map_err(|e| format!("unable to serialize header: {}", e))?;
|
||||
|
||||
s.write_all(&header_bytes).await?;
|
||||
s.write_all(&resp_bytes).await?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,12 +131,8 @@ async fn main() {
|
||||
};
|
||||
drop(term);
|
||||
|
||||
let ctx = Rc::new(RefCell::new(Context::new(
|
||||
config.greeter,
|
||||
config.greeter_user,
|
||||
vt,
|
||||
)));
|
||||
if let Err(e) = ctx.borrow_mut().greet().await {
|
||||
let ctx = Rc::new(Context::new(config.greeter, config.greeter_user, vt));
|
||||
if let Err(e) = ctx.greet().await {
|
||||
eprintln!("unable to start greeter: {}", e);
|
||||
reset_vt(vt).expect("unable to reset vt");
|
||||
std::process::exit(1);
|
||||
@@ -129,11 +147,7 @@ async fn main() {
|
||||
let mut alarm = signal(SignalKind::alarm()).expect("unable to listen for SIGALRM");
|
||||
loop {
|
||||
alarm.recv().await;
|
||||
alarm_ctx
|
||||
.borrow_mut()
|
||||
.alarm()
|
||||
.await
|
||||
.expect("unable to read alarm");
|
||||
alarm_ctx.alarm().await.expect("unable to read alarm");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -143,7 +157,6 @@ async fn main() {
|
||||
loop {
|
||||
child.recv().await;
|
||||
child_ctx
|
||||
.borrow_mut()
|
||||
.check_children()
|
||||
.await
|
||||
.expect("unable to check children");
|
||||
@@ -156,10 +169,7 @@ async fn main() {
|
||||
signal(SignalKind::terminate()).expect("unable to listen for SIGTERM");
|
||||
loop {
|
||||
term.recv().await;
|
||||
term_ctx
|
||||
.borrow_mut()
|
||||
.terminate()
|
||||
.expect("unable to terminate");
|
||||
term_ctx.terminate().await.expect("unable to terminate");
|
||||
}
|
||||
});
|
||||
|
||||
|
@@ -1,5 +1,3 @@
|
||||
use std::ffi::{CStr, CString};
|
||||
|
||||
/// A trait representing the PAM authentification conversation
|
||||
///
|
||||
/// PAM authentification is done as a conversation mechanism, in which PAM
|
||||
@@ -15,46 +13,14 @@ pub trait Converse {
|
||||
///
|
||||
/// This would typically be the username. The exact question is provided as the
|
||||
/// `msg` argument if you wish to display it to your user.
|
||||
fn prompt_echo<'a>(&'a mut self, msg: &CStr) -> ::std::result::Result<&'a CStr, ()>;
|
||||
fn prompt_echo(&self, msg: &str) -> ::std::result::Result<String, ()>;
|
||||
/// PAM requests a value that should be typed blindly by the user
|
||||
///
|
||||
/// This would typically be the password. The exact question is provided as the
|
||||
/// `msg` argument if you wish to display it to your user.
|
||||
fn prompt_blind<'a>(&'a mut self, msg: &CStr) -> ::std::result::Result<&'a CStr, ()>;
|
||||
fn prompt_blind(&self, msg: &str) -> ::std::result::Result<String, ()>;
|
||||
/// This is an informational message from PAM
|
||||
fn info(&mut self, msg: &CStr);
|
||||
fn info(&self, msg: &str) -> Result<(), ()>;
|
||||
/// This is an error message from PAM
|
||||
fn error(&mut self, msg: &CStr);
|
||||
}
|
||||
|
||||
/// A minimalistic conversation handler, that uses given login and password
|
||||
///
|
||||
/// This conversation handler is not really interactive, but simply returns to
|
||||
/// PAM the value that have been set using the `set_credentials` method.
|
||||
pub struct PasswordConv {
|
||||
login: CString,
|
||||
passwd: CString,
|
||||
}
|
||||
|
||||
impl PasswordConv {
|
||||
/// Create a new `PasswordConv` handler
|
||||
pub fn new(login: &str, password: &str) -> PasswordConv {
|
||||
PasswordConv {
|
||||
login: CString::new(login).unwrap(),
|
||||
passwd: CString::new(password).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Converse for PasswordConv {
|
||||
fn prompt_echo<'a>(&'a mut self, _msg: &CStr) -> Result<&'a CStr, ()> {
|
||||
Ok(&self.login)
|
||||
}
|
||||
fn prompt_blind<'a>(&'a mut self, _msg: &CStr) -> Result<&'a CStr, ()> {
|
||||
Ok(&self.passwd)
|
||||
}
|
||||
fn info(&mut self, _msg: &CStr) {}
|
||||
fn error(&mut self, msg: &CStr) {
|
||||
eprintln!("[PAM ERROR] {}", msg.to_string_lossy());
|
||||
}
|
||||
fn error(&self, msg: &str) -> Result<(), ()>;
|
||||
}
|
||||
|
@@ -1,13 +1,14 @@
|
||||
use std::ffi::CStr;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::mem;
|
||||
use std::pin::Pin;
|
||||
|
||||
use libc::{c_int, c_void, calloc, free, size_t, strdup};
|
||||
use pam_sys::{PamConversation, PamMessage, PamMessageStyle, PamResponse, PamReturnCode};
|
||||
|
||||
use super::converse::Converse;
|
||||
|
||||
pub struct PamConvHandlerWrapper {
|
||||
pub handler: Box<dyn Converse>,
|
||||
pub struct PamConvHandlerWrapper<'a> {
|
||||
pub handler: Pin<Box<dyn Converse + 'a>>,
|
||||
}
|
||||
|
||||
pub fn make_conversation(conv: &mut PamConvHandlerWrapper) -> PamConversation {
|
||||
@@ -31,7 +32,7 @@ pub extern "C" fn converse(
|
||||
return PamReturnCode::BUF_ERR as c_int;
|
||||
}
|
||||
|
||||
let wrapper = unsafe { &mut *(appdata_ptr as *mut PamConvHandlerWrapper) };
|
||||
let wrapper = unsafe { &*(appdata_ptr as *const PamConvHandlerWrapper) };
|
||||
|
||||
let mut result: PamReturnCode = PamReturnCode::SUCCESS;
|
||||
for i in 0..num_msg as isize {
|
||||
@@ -39,24 +40,43 @@ pub extern "C" fn converse(
|
||||
let m: &mut PamMessage = unsafe { &mut **(msg.offset(i)) };
|
||||
let r: &mut PamResponse = unsafe { &mut *(resp.offset(i)) };
|
||||
let msg = unsafe { CStr::from_ptr(m.msg) };
|
||||
let msg = match msg.to_str() {
|
||||
Ok(m) => m,
|
||||
Err(_) => {
|
||||
result = PamReturnCode::CONV_ERR;
|
||||
break;
|
||||
}
|
||||
};
|
||||
// match on msg_style
|
||||
match PamMessageStyle::from(m.msg_style) {
|
||||
PamMessageStyle::PROMPT_ECHO_ON => {
|
||||
if let Ok(handler_response) = wrapper.handler.prompt_echo(msg) {
|
||||
r.resp = unsafe { strdup(handler_response.as_ptr()) };
|
||||
let cstr =
|
||||
CString::new(handler_response).expect("unable to allocate response string");
|
||||
r.resp = unsafe { strdup(cstr.as_ptr()) };
|
||||
} else {
|
||||
result = PamReturnCode::CONV_ERR;
|
||||
}
|
||||
}
|
||||
PamMessageStyle::PROMPT_ECHO_OFF => {
|
||||
if let Ok(handler_response) = wrapper.handler.prompt_blind(msg) {
|
||||
r.resp = unsafe { strdup(handler_response.as_ptr()) };
|
||||
let cstr =
|
||||
CString::new(handler_response).expect("unable to allocate response string");
|
||||
r.resp = unsafe { strdup(cstr.as_ptr()) };
|
||||
} else {
|
||||
result = PamReturnCode::CONV_ERR;
|
||||
}
|
||||
}
|
||||
PamMessageStyle::ERROR_MSG => wrapper.handler.error(msg),
|
||||
PamMessageStyle::TEXT_INFO => wrapper.handler.info(msg),
|
||||
PamMessageStyle::ERROR_MSG => {
|
||||
if wrapper.handler.error(msg).is_err() {
|
||||
result = PamReturnCode::CONV_ERR;
|
||||
}
|
||||
}
|
||||
PamMessageStyle::TEXT_INFO => {
|
||||
if wrapper.handler.info(msg).is_err() {
|
||||
result = PamReturnCode::CONV_ERR;
|
||||
}
|
||||
}
|
||||
}
|
||||
if result != PamReturnCode::SUCCESS {
|
||||
break;
|
||||
|
@@ -1,6 +1,7 @@
|
||||
use std::error::Error;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::ptr;
|
||||
|
||||
use libc::c_void;
|
||||
@@ -12,20 +13,25 @@ use super::ffi::{make_conversation, PamConvHandlerWrapper};
|
||||
|
||||
pub struct PamSession<'a> {
|
||||
handle: &'a mut PamHandle,
|
||||
pub converse: Box<PamConvHandlerWrapper>,
|
||||
#[allow(unused)]
|
||||
lifetime_extender: Pin<Box<PamConvHandlerWrapper<'a>>>,
|
||||
last_code: PamReturnCode,
|
||||
}
|
||||
|
||||
impl<'a> PamSession<'a> {
|
||||
pub fn start(service: &str, pam_conv: Box<dyn Converse>) -> Result<PamSession, Box<dyn Error>> {
|
||||
let mut pch = Box::new(PamConvHandlerWrapper { handler: pam_conv });
|
||||
pub fn start(
|
||||
service: &str,
|
||||
user: &'a str,
|
||||
pam_conv: Pin<Box<dyn Converse + 'a>>,
|
||||
) -> Result<PamSession<'a>, Box<dyn Error>> {
|
||||
let mut pch = Box::pin(PamConvHandlerWrapper { handler: pam_conv });
|
||||
let conv = make_conversation(&mut *pch);
|
||||
let mut pam_handle: *mut PamHandle = ptr::null_mut();
|
||||
|
||||
match pam_sys::start(service, None, &conv, &mut pam_handle) {
|
||||
match pam_sys::start(service, Some(user), &conv, &mut pam_handle) {
|
||||
PamReturnCode::SUCCESS => Ok(PamSession {
|
||||
handle: unsafe { &mut *pam_handle },
|
||||
converse: pch,
|
||||
lifetime_extender: pch,
|
||||
last_code: PamReturnCode::SUCCESS,
|
||||
}),
|
||||
_ => Err(io::Error::new(io::ErrorKind::Other, "unable to start pam session").into()),
|
||||
|
@@ -5,51 +5,55 @@ use std::{
|
||||
ffi::CString,
|
||||
fs,
|
||||
fs::File,
|
||||
io::{BufRead, BufReader, Write},
|
||||
os::unix::io::{AsRawFd, FromRawFd, RawFd},
|
||||
io::{BufRead, BufReader},
|
||||
path::PathBuf,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use nix::{
|
||||
fcntl::{fcntl, FcntlArg},
|
||||
sys::{
|
||||
signal::{SigSet, Signal},
|
||||
wait::{waitpid, WaitPidFlag, WaitStatus},
|
||||
},
|
||||
unistd::{close, execve, fork, initgroups, setgid, setsid, setuid, ForkResult, Gid, Pid, Uid},
|
||||
unistd::{execve, fork, initgroups, setgid, setsid, setuid, ForkResult, Gid, Pid, Uid},
|
||||
};
|
||||
use pam_sys::{PamFlag, PamItemType};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use users::{os::unix::UserExt, User};
|
||||
use users::os::unix::UserExt;
|
||||
|
||||
use crate::{pam::converse::PasswordConv, pam::session::PamSession, terminal};
|
||||
use crate::{pam::converse::Converse, pam::session::PamSession, terminal};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
enum PamQuestionStyle {
|
||||
PromptEchoOff,
|
||||
PromptEchoOn,
|
||||
ErrorMsg,
|
||||
TextInfo,
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum QuestionStyle {
|
||||
Visible,
|
||||
Secret,
|
||||
Info,
|
||||
Error,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
enum ParentToSessionChild {
|
||||
InitiateLogin { username: String },
|
||||
PamResponse { resp: String, code: i32 },
|
||||
InitiateLogin {
|
||||
service: String,
|
||||
class: String,
|
||||
user: String,
|
||||
vt: usize,
|
||||
env: Vec<String>,
|
||||
cmd: Vec<String>,
|
||||
},
|
||||
PamResponse {
|
||||
resp: String,
|
||||
},
|
||||
Cancel,
|
||||
Start,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
enum SessionChildToParent {
|
||||
PamMessage { style: QuestionStyle, msg: String },
|
||||
PamAuthSuccess,
|
||||
Error { error: String },
|
||||
FinalChildPid(u64),
|
||||
PamMessage {
|
||||
style: PamQuestionStyle,
|
||||
msg: String,
|
||||
},
|
||||
LoginFinalized {
|
||||
success: bool,
|
||||
error_msg: String,
|
||||
},
|
||||
}
|
||||
|
||||
/// Returns a set containing the signals we want to block in the main process.
|
||||
@@ -63,15 +67,9 @@ pub fn blocked_sigset() -> SigSet {
|
||||
mask
|
||||
}
|
||||
|
||||
fn dup_fd_cloexec(fd: RawFd) -> Result<RawFd, Box<dyn Error>> {
|
||||
match fcntl(fd, FcntlArg::F_DUPFD_CLOEXEC(0)) {
|
||||
Ok(fd) => Ok(fd),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// SessionChild tracks the processes spawned by a session
|
||||
pub struct SessionChild {
|
||||
service: String,
|
||||
opened: Instant,
|
||||
task: Pid,
|
||||
sub_task: Pid,
|
||||
@@ -94,6 +92,10 @@ impl SessionChild {
|
||||
let _ = nix::sys::signal::kill(self.task, Signal::SIGKILL);
|
||||
}
|
||||
|
||||
pub fn get_service(&self) -> &str {
|
||||
&self.service
|
||||
}
|
||||
|
||||
/// Terminate a session. Sends SIGTERM in a loop, then sends SIGKILL in a loop.
|
||||
pub fn shoo(&self) {
|
||||
let task = self.sub_task;
|
||||
@@ -135,15 +137,12 @@ impl SessionChild {
|
||||
}
|
||||
|
||||
/// A device to initiate a logged in PAM session.
|
||||
pub struct Session<'a> {
|
||||
pub struct Session {
|
||||
service: String,
|
||||
opened: Instant,
|
||||
pam: PamSession<'a>,
|
||||
user: User,
|
||||
|
||||
class: String,
|
||||
vt: usize,
|
||||
env: Vec<String>,
|
||||
cmd: Vec<String>,
|
||||
task: Pid,
|
||||
sock: tokio::net::UnixDatagram,
|
||||
last_msg: Option<SessionChildToParent>,
|
||||
}
|
||||
|
||||
fn split_env<'a>(s: &'a str) -> Option<(&'a str, &str)> {
|
||||
@@ -156,7 +155,373 @@ fn split_env<'a>(s: &'a str) -> Option<(&'a str, &str)> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Session<'a> {
|
||||
pub struct SessionConv<'a> {
|
||||
sock: &'a std::os::unix::net::UnixDatagram,
|
||||
}
|
||||
|
||||
impl<'a> SessionConv<'a> {
|
||||
fn question(&self, msg: &str, style: QuestionStyle) -> Result<String, ()> {
|
||||
let msg = SessionChildToParent::PamMessage {
|
||||
style,
|
||||
msg: msg.to_string(),
|
||||
};
|
||||
let data = serde_json::to_vec(&msg).map_err(|e| eprintln!("pam_conv: {}", e))?;
|
||||
self.sock
|
||||
.send(&data)
|
||||
.map_err(|e| eprintln!("pam_conv: {}", e))?;
|
||||
|
||||
let mut data = [0; 1024];
|
||||
let len = self
|
||||
.sock
|
||||
.recv(&mut data[..])
|
||||
.map_err(|e| eprintln!("pam_conv: {}", e))?;
|
||||
let msg = serde_json::from_slice(&data[..len]).map_err(|e| eprintln!("pam_conv: {}", e))?;
|
||||
|
||||
match msg {
|
||||
ParentToSessionChild::PamResponse { resp, .. } => Ok(resp),
|
||||
ParentToSessionChild::Cancel => Err(()),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `PasswordConv` handler
|
||||
pub fn new(sock: &'a std::os::unix::net::UnixDatagram) -> SessionConv {
|
||||
SessionConv { sock }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Converse for SessionConv<'a> {
|
||||
fn prompt_echo(&self, msg: &str) -> Result<String, ()> {
|
||||
self.question(msg, QuestionStyle::Visible)
|
||||
}
|
||||
fn prompt_blind(&self, msg: &str) -> Result<String, ()> {
|
||||
self.question(msg, QuestionStyle::Secret)
|
||||
}
|
||||
fn info(&self, msg: &str) -> Result<(), ()> {
|
||||
self.question(msg, QuestionStyle::Info).map(|_| ())
|
||||
}
|
||||
fn error(&self, msg: &str) -> Result<(), ()> {
|
||||
self.question(msg, QuestionStyle::Error).map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
/// Process environment.d folders to generate the configured environment for
|
||||
/// the session.
|
||||
fn generate_user_environment(pam: &mut PamSession, home: String) -> Result<(), Box<dyn Error>> {
|
||||
let dirs = [
|
||||
"/usr/lib/environments.d/",
|
||||
"/usr/local/lib/environments.d/",
|
||||
"/run/environments.d/",
|
||||
"/etc/environments.d/",
|
||||
&format!("{}/.config/environment.d", home),
|
||||
];
|
||||
|
||||
let mut env: HashMap<String, String> = HashMap::new();
|
||||
for dir in dirs.iter() {
|
||||
let entries = match fs::read_dir(dir) {
|
||||
Ok(e) => e,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
let mut filepaths: Vec<PathBuf> =
|
||||
entries.filter_map(Result::ok).map(|e| e.path()).collect();
|
||||
|
||||
filepaths.sort();
|
||||
|
||||
for filepath in filepaths.into_iter() {
|
||||
let reader = BufReader::new(match File::open(&filepath) {
|
||||
Ok(f) => f,
|
||||
Err(_) => continue,
|
||||
});
|
||||
|
||||
for line in reader.lines().filter_map(Result::ok) {
|
||||
let (key, value) = match split_env(&line) {
|
||||
Some(v) => v,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
if key.starts_with('#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !value.contains('$') {
|
||||
env.insert(key.to_string(), value.to_string());
|
||||
continue;
|
||||
}
|
||||
|
||||
let reference = &value[1..];
|
||||
let value = match env.get(reference) {
|
||||
Some(v) => v.to_string(),
|
||||
None => match pam.getenv(reference) {
|
||||
Some(pam_val) => match split_env(pam_val) {
|
||||
Some((_, new_value)) => new_value.to_string(),
|
||||
None => "".to_string(),
|
||||
},
|
||||
None => "".to_string(),
|
||||
},
|
||||
};
|
||||
|
||||
env.insert(key.to_string(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let env = env
|
||||
.into_iter()
|
||||
.map(|(key, value)| format!("{}={}", key, value));
|
||||
|
||||
for e in env {
|
||||
pam.putenv(&e)
|
||||
.map_err(|e| format!("unable to set PAM environment: {}", e))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The entry point for the session worker process. The session worker is
|
||||
/// responsible for the entirety of the session setup and execution. It is
|
||||
/// started by Session::start.
|
||||
fn session_worker(sock: &std::os::unix::net::UnixDatagram) -> Result<(), Box<dyn Error>> {
|
||||
let mut data = [0; 1024];
|
||||
let len = sock
|
||||
.recv(&mut data[..])
|
||||
.map_err(|e| format!("unable to recieve message: {}", e))?;
|
||||
let msg = serde_json::from_slice(&data[..len])
|
||||
.map_err(|e| format!("unable to deserialize message: {}", e))?;
|
||||
let (service, class, user, vt, env, cmd) = match msg {
|
||||
ParentToSessionChild::InitiateLogin {
|
||||
service,
|
||||
class,
|
||||
user,
|
||||
vt,
|
||||
env,
|
||||
cmd,
|
||||
} => (service, class, user, vt, env, cmd),
|
||||
ParentToSessionChild::Cancel => return Err("cancelled".into()),
|
||||
_ => return Err("unexpected message".into()),
|
||||
};
|
||||
|
||||
let conv = Box::pin(SessionConv::new(sock));
|
||||
let mut pam = PamSession::start(&service, &user, conv)
|
||||
.map_err(|e| format!("unable to initiate PAM session: {}", e))?;
|
||||
|
||||
pam.authenticate(PamFlag::NONE)
|
||||
.map_err(|e| format!("unable to authenticate account: {}", e))?;
|
||||
pam.acct_mgmt(PamFlag::NONE)
|
||||
.map_err(|e| format!("unable to validate account: {}", e))?;
|
||||
|
||||
let msg = SessionChildToParent::PamAuthSuccess;
|
||||
let out =
|
||||
serde_json::to_vec(&msg).map_err(|e| format!("unable to serialize message: {}", e))?;
|
||||
sock.send(&out)
|
||||
.map_err(|e| format!("unable to send message: {}", e))?;
|
||||
|
||||
let len = sock
|
||||
.recv(&mut data[..])
|
||||
.map_err(|e| format!("unable to recieve message: {}", e))?;
|
||||
let msg = serde_json::from_slice(&data[..len])
|
||||
.map_err(|e| format!("unable to deserialize message: {}", e))?;
|
||||
match msg {
|
||||
ParentToSessionChild::Start => (),
|
||||
ParentToSessionChild::Cancel => return Err("cancelled".into()),
|
||||
_ => return Err("unexpected message".into()),
|
||||
}
|
||||
|
||||
let pam_username = pam.get_user()?;
|
||||
|
||||
let user = users::get_user_by_name(&pam_username).ok_or("unable to get user info")?;
|
||||
|
||||
// Clear the signal masking that was inherited from the parent.
|
||||
blocked_sigset()
|
||||
.thread_unblock()
|
||||
.map_err(|e| format!("unable to unblock signals: {}", e))?;
|
||||
|
||||
// Make this process a session leader.
|
||||
setsid().map_err(|e| format!("unable to become session leader: {}", e))?;
|
||||
|
||||
// Opening our target terminal. This will automatically make it our
|
||||
// controlling terminal. An attempt was made to use TIOCSCTTY to do
|
||||
// this explicitly, but it neither worked nor was worth the additional
|
||||
// code.
|
||||
let mut target_term = terminal::Terminal::open(vt)?;
|
||||
|
||||
// Clear TTY so that it will be empty when we switch to it.
|
||||
target_term.term_clear()?;
|
||||
|
||||
// Set the target VT mode to text for compatibility. Other login
|
||||
// managers set this to graphics, but that disallows start of textual
|
||||
// applications, which greetd aims to support.
|
||||
target_term.kd_setmode(terminal::KdMode::Text)?;
|
||||
|
||||
// A bit more work if a VT switch is required.
|
||||
if vt != target_term.vt_get_current()? {
|
||||
// Perform a switch to the target VT, simultaneously resetting it to
|
||||
// VT_AUTO.
|
||||
eprintln!("session worker: switching to VT {}", vt);
|
||||
target_term.vt_setactivate(vt)?;
|
||||
eprintln!("session worker: switch to VT {} complete", vt);
|
||||
}
|
||||
|
||||
// Hook up std(in|out|err). This allows us to run console applications.
|
||||
// Also, hooking up stdin is required, as applications otherwise fail to
|
||||
// start, both for graphical and console-based applications. I do not
|
||||
// know why this is the case.
|
||||
target_term.term_connect_pipes()?;
|
||||
|
||||
// We no longer need these, so close them to avoid inheritance.
|
||||
drop(target_term);
|
||||
|
||||
// Prepare some values from the user struct we gathered earlier.
|
||||
let username = user.name().to_str().unwrap_or("");
|
||||
let home = user.home_dir().to_str().unwrap_or("");
|
||||
let shell = user.shell().to_str().unwrap_or("");
|
||||
let uid = Uid::from_raw(user.uid());
|
||||
let gid = Gid::from_raw(user.primary_group_id());
|
||||
|
||||
// PAM has to be provided a bunch of environment variables before
|
||||
// open_session. We pass any environment variables from our greeter
|
||||
// through here as well. This allows them to affect PAM (more
|
||||
// specifically, pam_systemd.so), as well as make it easier to gather
|
||||
// and set all environment variables later.
|
||||
let prepared_env = [
|
||||
"XDG_SEAT=seat0".to_string(),
|
||||
format!("XDG_SESSION_CLASS={}", class),
|
||||
format!("XDG_VTNR={}", vt),
|
||||
format!("USER={}", username),
|
||||
format!("LOGNAME={}", username),
|
||||
format!("HOME={}", home),
|
||||
format!("SHELL={}", shell),
|
||||
];
|
||||
|
||||
for e in prepared_env.iter().chain(env.iter()) {
|
||||
pam.putenv(e)
|
||||
.map_err(|e| format!("unable to set PAM environment: {}", e))?;
|
||||
}
|
||||
|
||||
// Tell PAM what TTY we're targetting, which is used by logind.
|
||||
pam.set_item(PamItemType::TTY, &format!("/dev/tty{}", vt))
|
||||
.map_err(|e| format!("unable to set PAM TTY item: {}", e))?;
|
||||
|
||||
// Not the credentials you think.
|
||||
pam.setcred(PamFlag::ESTABLISH_CRED)
|
||||
.map_err(|e| format!("unable to establish PAM credentials: {}", e))?;
|
||||
|
||||
// Session time!
|
||||
pam.open_session(PamFlag::NONE)
|
||||
.map_err(|e| format!("unable to open PAM session: {}", e))?;
|
||||
|
||||
// Prepare some strings in C format that we'll need.
|
||||
let cusername = CString::new(username)?;
|
||||
let command = format!("[ -f /etc/profile ] && source /etc/profile; [ -f $HOME/.profile ] && source $HOME/.profile; exec {}", cmd.join(" "));
|
||||
|
||||
// Change working directory
|
||||
let pwd = match env::set_current_dir(home) {
|
||||
Ok(_) => home,
|
||||
Err(_) => {
|
||||
env::set_current_dir("/")
|
||||
.map_err(|e| format!("unable to set working directory: {}", e))?;
|
||||
"/"
|
||||
}
|
||||
};
|
||||
|
||||
// Check to see if a few necessary variables are there and patch things
|
||||
// up as needed.
|
||||
let mut fixup_env = vec![
|
||||
format!("PWD={}", pwd),
|
||||
format!("GREETD_SOCK={}", env::var("GREETD_SOCK").unwrap()),
|
||||
];
|
||||
if !pam.hasenv("TERM") {
|
||||
fixup_env.push("TERM=linux".to_string());
|
||||
}
|
||||
if !pam.hasenv("XDG_RUNTIME_DIR") {
|
||||
fixup_env.push(format!("XDG_RUNTIME_DIR=/run/user/{}", uid));
|
||||
}
|
||||
for e in fixup_env.into_iter() {
|
||||
pam.putenv(&e)
|
||||
.map_err(|e| format!("unable to set PAM environment: {}", e))?;
|
||||
}
|
||||
|
||||
// We're almost done with our environment. Let's go through
|
||||
// environment.d configuration to fix up the last bits.
|
||||
let home = home.to_string();
|
||||
generate_user_environment(&mut pam, home)?;
|
||||
|
||||
// Extract PAM environment for use with execve below.
|
||||
let pamenvlist = pam
|
||||
.getenvlist()
|
||||
.map_err(|e| format!("unable to get PAM environment: {}", e))?;
|
||||
let envvec = pamenvlist.to_vec();
|
||||
|
||||
// PAM is weird and gets upset if you exec from the process that opened
|
||||
// the session, registering it automatically as a log-out. Thus, we must
|
||||
// exec in a new child.
|
||||
let child = match fork().map_err(|e| format!("unable to fork: {}", e))? {
|
||||
ForkResult::Parent { child, .. } => child,
|
||||
ForkResult::Child => {
|
||||
// It is important that we do *not* return from here by
|
||||
// accidentally using '?'. The process *must* exit from within
|
||||
// this match arm.
|
||||
|
||||
// Drop privileges to target user
|
||||
initgroups(&cusername, gid).expect("unable to init groups");
|
||||
setgid(gid).expect("unable to set GID");
|
||||
setuid(uid).expect("unable to set UID");
|
||||
|
||||
// Run
|
||||
let cpath = CString::new("/bin/sh").unwrap();
|
||||
execve(
|
||||
&cpath,
|
||||
&[
|
||||
&cpath,
|
||||
&CString::new("-c").unwrap(),
|
||||
&CString::new(command).unwrap(),
|
||||
],
|
||||
&envvec,
|
||||
)
|
||||
.expect("unable to exec");
|
||||
|
||||
unreachable!("after exec");
|
||||
}
|
||||
};
|
||||
|
||||
// Signal the inner PID to the parent process.
|
||||
let msg = SessionChildToParent::FinalChildPid(child.as_raw() as u64);
|
||||
let data = serde_json::to_vec(&msg)?;
|
||||
sock.send(&data)
|
||||
.map_err(|e| format!("unable to send message: {}", e))?;
|
||||
sock.shutdown(std::net::Shutdown::Both)
|
||||
.map_err(|e| format!("unable to close pipe: {}", e))?;
|
||||
|
||||
// Wait for process to terminate, handling EINTR as necessary.
|
||||
loop {
|
||||
match waitpid(child, None) {
|
||||
Err(nix::Error::Sys(nix::errno::Errno::EINTR)) => continue,
|
||||
Err(e) => eprintln!("session: waitpid on inner child failed: {}", e),
|
||||
Ok(_) => break,
|
||||
}
|
||||
}
|
||||
|
||||
// Close the session. This step requires root privileges to run, as it
|
||||
// will result in various forms of login teardown (including unmounting
|
||||
// home folders, telling logind that the session ended, etc.). This is
|
||||
// why we cannot drop privileges in this process, but must do it in the
|
||||
// inner-most child.
|
||||
pam.close_session(PamFlag::NONE)
|
||||
.map_err(|e| format!("unable to close PAM session: {}", e))?;
|
||||
pam.setcred(PamFlag::DELETE_CRED)
|
||||
.map_err(|e| format!("unable to clear PAM credentials: {}", e))?;
|
||||
pam.end()
|
||||
.map_err(|e| format!("unable to clear PAM credentials: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub enum SessionState {
|
||||
Question(QuestionStyle, String),
|
||||
Ready,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
///
|
||||
/// Create a Session object with details required to start the specified
|
||||
/// session. Surceeds if the service accepts the credentials.
|
||||
@@ -165,318 +530,14 @@ impl<'a> Session<'a> {
|
||||
/// authentication attempt. If successful, this same PAM handle will later
|
||||
/// be used to tart the session.
|
||||
///
|
||||
pub fn new(
|
||||
service: &'a str,
|
||||
pub async fn new(
|
||||
service: &str,
|
||||
class: &str,
|
||||
username: &str,
|
||||
password: &str,
|
||||
user: &str,
|
||||
cmd: Vec<String>,
|
||||
provided_env: Vec<String>,
|
||||
env: Vec<String>,
|
||||
vt: usize,
|
||||
) -> Result<Session<'a>, Box<dyn Error>> {
|
||||
let pass_conv = Box::new(PasswordConv::new(username, password));
|
||||
let mut pam_session = PamSession::start(service, pass_conv)?;
|
||||
pam_session.authenticate(PamFlag::NONE)?;
|
||||
pam_session.acct_mgmt(PamFlag::NONE)?;
|
||||
|
||||
let pam_username = pam_session.get_user()?;
|
||||
|
||||
let user = users::get_user_by_name(&pam_username).ok_or("unable to get user info")?;
|
||||
|
||||
// Return our description of this session.
|
||||
Ok(Session {
|
||||
opened: Instant::now(),
|
||||
pam: pam_session,
|
||||
class: class.to_string(),
|
||||
env: provided_env,
|
||||
user,
|
||||
vt,
|
||||
cmd,
|
||||
})
|
||||
}
|
||||
|
||||
/// Process environment.d folders to generate the configured environment for
|
||||
/// the session.
|
||||
fn generate_user_environment(&mut self, home: String) -> Result<(), Box<dyn Error>> {
|
||||
let dirs = [
|
||||
"/usr/lib/environments.d/",
|
||||
"/usr/local/lib/environments.d/",
|
||||
"/run/environments.d/",
|
||||
"/etc/environments.d/",
|
||||
&format!("{}/.config/environment.d", home),
|
||||
];
|
||||
|
||||
let mut env: HashMap<String, String> = HashMap::new();
|
||||
for dir in dirs.iter() {
|
||||
let entries = match fs::read_dir(dir) {
|
||||
Ok(e) => e,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
let mut filepaths: Vec<PathBuf> =
|
||||
entries.filter_map(Result::ok).map(|e| e.path()).collect();
|
||||
|
||||
filepaths.sort();
|
||||
|
||||
for filepath in filepaths.into_iter() {
|
||||
let reader = BufReader::new(match File::open(&filepath) {
|
||||
Ok(f) => f,
|
||||
Err(_) => continue,
|
||||
});
|
||||
|
||||
for line in reader.lines().filter_map(Result::ok) {
|
||||
let (key, value) = match split_env(&line) {
|
||||
Some(v) => v,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
if key.starts_with('#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !value.contains('$') {
|
||||
env.insert(key.to_string(), value.to_string());
|
||||
continue;
|
||||
}
|
||||
|
||||
let reference = &value[1..];
|
||||
let value = match env.get(reference) {
|
||||
Some(v) => v.to_string(),
|
||||
None => match self.pam.getenv(reference) {
|
||||
Some(pam_val) => match split_env(pam_val) {
|
||||
Some((_, new_value)) => new_value.to_string(),
|
||||
None => "".to_string(),
|
||||
},
|
||||
None => "".to_string(),
|
||||
},
|
||||
};
|
||||
|
||||
env.insert(key.to_string(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let env = env
|
||||
.into_iter()
|
||||
.map(|(key, value)| format!("{}={}", key, value));
|
||||
|
||||
for e in env {
|
||||
self.pam
|
||||
.putenv(&e)
|
||||
.map_err(|e| format!("unable to set PAM environment: {}", e))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The entry point for the session worker process. The session worker is
|
||||
/// responsible for the entirety of the session setup and execution. It is
|
||||
/// started by Session::start.
|
||||
fn session_worker(
|
||||
&mut self,
|
||||
childfd: std::os::unix::net::UnixDatagram,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
// Clear the signal masking that was inherited from the parent.
|
||||
blocked_sigset()
|
||||
.thread_unblock()
|
||||
.map_err(|e| format!("unable to unblock signals: {}", e))?;
|
||||
|
||||
// Make this process a session leader.
|
||||
setsid().map_err(|e| format!("unable to become session leader: {}", e))?;
|
||||
|
||||
// Opening our target terminal. This will automatically make it our
|
||||
// controlling terminal. An attempt was made to use TIOCSCTTY to do
|
||||
// this explicitly, but it neither worked nor was worth the additional
|
||||
// code.
|
||||
let mut target_term = terminal::Terminal::open(self.vt)?;
|
||||
|
||||
// Clear TTY so that it will be empty when we switch to it.
|
||||
target_term.term_clear()?;
|
||||
|
||||
// Set the target VT mode to text for compatibility. Other login
|
||||
// managers set this to graphics, but that disallows start of textual
|
||||
// applications, which greetd aims to support.
|
||||
target_term.kd_setmode(terminal::KdMode::Text)?;
|
||||
|
||||
// A bit more work if a VT switch is required.
|
||||
if self.vt != target_term.vt_get_current()? {
|
||||
// Perform a switch to the target VT, simultaneously resetting it to
|
||||
// VT_AUTO.
|
||||
eprintln!("session worker: switching to VT {}", self.vt);
|
||||
target_term.vt_setactivate(self.vt)?;
|
||||
eprintln!("session worker: switch to VT {} complete", self.vt);
|
||||
}
|
||||
|
||||
// Hook up std(in|out|err). This allows us to run console applications.
|
||||
// Also, hooking up stdin is required, as applications otherwise fail to
|
||||
// start, both for graphical and console-based applications. I do not
|
||||
// know why this is the case.
|
||||
target_term.term_connect_pipes()?;
|
||||
|
||||
// We no longer need these, so close them to avoid inheritance.
|
||||
drop(target_term);
|
||||
|
||||
// Prepare some values from the user struct we gathered earlier.
|
||||
let username = self.user.name().to_str().unwrap_or("");
|
||||
let home = self.user.home_dir().to_str().unwrap_or("");
|
||||
let shell = self.user.shell().to_str().unwrap_or("");
|
||||
let uid = Uid::from_raw(self.user.uid());
|
||||
let gid = Gid::from_raw(self.user.primary_group_id());
|
||||
|
||||
// PAM has to be provided a bunch of environment variables before
|
||||
// open_session. We pass any environment variables from our greeter
|
||||
// through here as well. This allows them to affect PAM (more
|
||||
// specifically, pam_systemd.so), as well as make it easier to gather
|
||||
// and set all environment variables later.
|
||||
let prepared_env = [
|
||||
"XDG_SEAT=seat0".to_string(),
|
||||
format!("XDG_SESSION_CLASS={}", self.class),
|
||||
format!("XDG_VTNR={}", self.vt),
|
||||
format!("USER={}", username),
|
||||
format!("LOGNAME={}", username),
|
||||
format!("HOME={}", home),
|
||||
format!("SHELL={}", shell),
|
||||
];
|
||||
|
||||
for e in prepared_env.iter().chain(self.env.iter()) {
|
||||
self.pam
|
||||
.putenv(e)
|
||||
.map_err(|e| format!("unable to set PAM environment: {}", e))?;
|
||||
}
|
||||
|
||||
// Tell PAM what TTY we're targetting, which is used by logind.
|
||||
self.pam
|
||||
.set_item(PamItemType::TTY, &format!("/dev/tty{}", self.vt))
|
||||
.map_err(|e| format!("unable to set PAM TTY item: {}", e))?;
|
||||
|
||||
// Not the credentials you think.
|
||||
self.pam
|
||||
.setcred(PamFlag::ESTABLISH_CRED)
|
||||
.map_err(|e| format!("unable to establish PAM credentials: {}", e))?;
|
||||
|
||||
// Session time!
|
||||
self.pam
|
||||
.open_session(PamFlag::NONE)
|
||||
.map_err(|e| format!("unable to open PAM session: {}", e))?;
|
||||
|
||||
// Prepare some strings in C format that we'll need.
|
||||
let cusername = CString::new(username)?;
|
||||
let command = format!("[ -f /etc/profile ] && source /etc/profile; [ -f $HOME/.profile ] && source $HOME/.profile; exec {}", self.cmd.join(" "));
|
||||
|
||||
// Change working directory
|
||||
let pwd = match env::set_current_dir(home) {
|
||||
Ok(_) => home,
|
||||
Err(_) => {
|
||||
env::set_current_dir("/")
|
||||
.map_err(|e| format!("unable to set working directory: {}", e))?;
|
||||
"/"
|
||||
}
|
||||
};
|
||||
|
||||
// Check to see if a few necessary variables are there and patch things
|
||||
// up as needed.
|
||||
let mut fixup_env = vec![
|
||||
format!("PWD={}", pwd),
|
||||
format!("GREETD_SOCK={}", env::var("GREETD_SOCK").unwrap()),
|
||||
];
|
||||
if !self.pam.hasenv("TERM") {
|
||||
fixup_env.push("TERM=linux".to_string());
|
||||
}
|
||||
if !self.pam.hasenv("XDG_RUNTIME_DIR") {
|
||||
fixup_env.push(format!("XDG_RUNTIME_DIR=/run/user/{}", uid));
|
||||
}
|
||||
for e in fixup_env.into_iter() {
|
||||
self.pam
|
||||
.putenv(&e)
|
||||
.map_err(|e| format!("unable to set PAM environment: {}", e))?;
|
||||
}
|
||||
|
||||
// We're almost done with our environment. Let's go through
|
||||
// environment.d configuration to fix up the last bits.
|
||||
let home = home.to_string();
|
||||
self.generate_user_environment(home)?;
|
||||
|
||||
// Extract PAM environment for use with execve below.
|
||||
let pamenvlist = self
|
||||
.pam
|
||||
.getenvlist()
|
||||
.map_err(|e| format!("unable to get PAM environment: {}", e))?;
|
||||
let envvec = pamenvlist.to_vec();
|
||||
|
||||
// PAM is weird and gets upset if you exec from the process that opened
|
||||
// the session, registering it automatically as a log-out. Thus, we must
|
||||
// exec in a new child.
|
||||
let child = match fork().map_err(|e| format!("unable to fork: {}", e))? {
|
||||
ForkResult::Parent { child, .. } => child,
|
||||
ForkResult::Child => {
|
||||
// It is important that we do *not* return from here by
|
||||
// accidentally using '?'. The process *must* exit from within
|
||||
// this match arm.
|
||||
|
||||
// Drop privileges to target user
|
||||
initgroups(&cusername, gid).expect("unable to init groups");
|
||||
setgid(gid).expect("unable to set GID");
|
||||
setuid(uid).expect("unable to set UID");
|
||||
|
||||
// Run
|
||||
close(childfd.as_raw_fd()).expect("unable to close pipe");
|
||||
let cpath = CString::new("/bin/sh").unwrap();
|
||||
execve(
|
||||
&cpath,
|
||||
&[
|
||||
&cpath,
|
||||
&CString::new("-c").unwrap(),
|
||||
&CString::new(command).unwrap(),
|
||||
],
|
||||
&envvec,
|
||||
)
|
||||
.expect("unable to exec");
|
||||
|
||||
unreachable!("after exec");
|
||||
}
|
||||
};
|
||||
|
||||
// Signal the inner PID to the parent process.
|
||||
let msg = SessionChildToParent::FinalChildPid(child.as_raw() as u64);
|
||||
let data = serde_json::to_vec(&msg)?;
|
||||
eprintln!("sending: {:?}\n", data);
|
||||
childfd
|
||||
.send(&data)
|
||||
.map_err(|e| format!("unable to send message: {}", e))?;
|
||||
// close(childfd.as_raw_fd())
|
||||
// .map_err(|e| format!("unable to close pipe: {}", e))?;
|
||||
|
||||
// Wait for process to terminate, handling EINTR as necessary.
|
||||
loop {
|
||||
match waitpid(child, None) {
|
||||
Err(nix::Error::Sys(nix::errno::Errno::EINTR)) => continue,
|
||||
Err(e) => eprintln!("session: waitpid on inner child failed: {}", e),
|
||||
Ok(_) => break,
|
||||
}
|
||||
}
|
||||
|
||||
// Close the session. This step requires root privileges to run, as it
|
||||
// will result in various forms of login teardown (including unmounting
|
||||
// home folders, telling logind that the session ended, etc.). This is
|
||||
// why we cannot drop privileges in this process, but must do it in the
|
||||
// inner-most child.
|
||||
self.pam
|
||||
.close_session(PamFlag::NONE)
|
||||
.map_err(|e| format!("unable to close PAM session: {}", e))?;
|
||||
self.pam
|
||||
.setcred(PamFlag::DELETE_CRED)
|
||||
.map_err(|e| format!("unable to clear PAM credentials: {}", e))?;
|
||||
self.pam
|
||||
.end()
|
||||
.map_err(|e| format!("unable to clear PAM credentials: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Start the session described within the Session.
|
||||
///
|
||||
pub async fn start(&mut self) -> Result<SessionChild, Box<dyn Error>> {
|
||||
) -> Result<Session, Box<dyn Error>> {
|
||||
// Pipe used to communicate the true PID of the final child.
|
||||
let (parentfd, childfd) = std::os::unix::net::UnixDatagram::pair()
|
||||
.map_err(|e| format!("could not create pipe: {}", e))?;
|
||||
@@ -484,52 +545,116 @@ impl<'a> Session<'a> {
|
||||
// PAM requires for unfathmoable reasons that we run this in a
|
||||
// subprocess. Things seem to fail otherwise.
|
||||
let child = match fork()? {
|
||||
ForkResult::Parent { child, .. } => {
|
||||
close(childfd.as_raw_fd())?;
|
||||
child
|
||||
}
|
||||
ForkResult::Parent { child, .. } => child,
|
||||
ForkResult::Child => {
|
||||
// It is important that we do *not* return from here by
|
||||
// accidentally using '?'. The process *must* exit from within
|
||||
// this match arm.
|
||||
|
||||
// Close our side of the pipe.
|
||||
close(parentfd.as_raw_fd()).expect("unable to close parent pipe");
|
||||
|
||||
// Keep our old stderr around for logging, but CLOEXEC so that
|
||||
// we are not poluting the new child.
|
||||
let mut stderr = unsafe { File::from_raw_fd(dup_fd_cloexec(2 as RawFd).unwrap()) };
|
||||
|
||||
// Run the child entrypoint.
|
||||
if let Err(e) = self.session_worker(childfd) {
|
||||
writeln!(stderr, "session worker: {}", e).expect("could not write log output");
|
||||
if let Err(e) = session_worker(&childfd) {
|
||||
let msg = SessionChildToParent::Error {
|
||||
error: format!("{}", e),
|
||||
};
|
||||
let data = serde_json::to_vec(&msg).expect("unable to serialize message");
|
||||
childfd.send(&data).expect("unable to send message");
|
||||
std::process::exit(1);
|
||||
}
|
||||
std::process::exit(0);
|
||||
}
|
||||
};
|
||||
|
||||
let mut parentfd = tokio::net::UnixDatagram::from_std(parentfd)?;
|
||||
let mut sock = tokio::net::UnixDatagram::from_std(parentfd)?;
|
||||
|
||||
// We have no use for the PAM handle in the host process anymore
|
||||
self.pam.setcred(PamFlag::DELETE_CRED)?;
|
||||
self.pam.end()?;
|
||||
let msg = ParentToSessionChild::InitiateLogin {
|
||||
service: service.to_string(),
|
||||
class: class.to_string(),
|
||||
user: user.to_string(),
|
||||
vt,
|
||||
env,
|
||||
cmd,
|
||||
};
|
||||
let data = serde_json::to_vec(&msg)?;
|
||||
sock.send(&data)
|
||||
.await
|
||||
.map_err(|e| format!("unable to send worker process request: {}", e))?;
|
||||
|
||||
Ok(Session {
|
||||
service: service.to_string(),
|
||||
opened: Instant::now(),
|
||||
task: child,
|
||||
sock,
|
||||
last_msg: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_state(&mut self) -> Result<SessionState, Box<dyn Error>> {
|
||||
let msg = match self.last_msg.take() {
|
||||
Some(msg) => msg,
|
||||
None => {
|
||||
let mut data = [0u8; 1024];
|
||||
let len = self.sock.recv(&mut data[..]).await?;
|
||||
serde_json::from_slice(&data[..len])
|
||||
.map_err(|e| format!("unable to read worker process response: {}", e))?
|
||||
}
|
||||
};
|
||||
|
||||
self.last_msg = Some(msg.clone());
|
||||
|
||||
match msg {
|
||||
SessionChildToParent::PamMessage { style, msg } => {
|
||||
Ok(SessionState::Question(style, msg))
|
||||
}
|
||||
SessionChildToParent::PamAuthSuccess => Ok(SessionState::Ready),
|
||||
SessionChildToParent::Error { error } => Err(error.into()),
|
||||
_ => Err("unexpected message from session worker".into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn post_answer(&mut self, answer: Option<String>) -> Result<(), Box<dyn Error>> {
|
||||
self.last_msg = None;
|
||||
let msg = match answer {
|
||||
Some(resp) => ParentToSessionChild::PamResponse { resp },
|
||||
None => ParentToSessionChild::Cancel,
|
||||
};
|
||||
let data = serde_json::to_vec(&msg)?;
|
||||
self.sock.send(&data).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Start the session described within the Session.
|
||||
///
|
||||
pub async fn start(&mut self) -> Result<SessionChild, Box<dyn Error>> {
|
||||
let msg = ParentToSessionChild::Start;
|
||||
let data = serde_json::to_vec(&msg)
|
||||
.map_err(|e| format!("unable to serialize worker process request: {}", e))?;
|
||||
self.sock
|
||||
.send(&data)
|
||||
.await
|
||||
.map_err(|e| format!("unable to send worker process request: {}", e))?;
|
||||
|
||||
let mut data = [0u8; 1024];
|
||||
let len = self
|
||||
.sock
|
||||
.recv(&mut data[..])
|
||||
.await
|
||||
.map_err(|e| format!("unable to receive worker process response: {}", e))?;
|
||||
let msg = serde_json::from_slice(&data[..len])
|
||||
.map_err(|e| format!("unable to deserialize worker process response: {}", e))?;
|
||||
|
||||
self.sock.shutdown(std::net::Shutdown::Both)?;
|
||||
|
||||
// Read the true child PID.
|
||||
let mut data = Vec::new();
|
||||
parentfd.recv(&mut data).await?;
|
||||
eprintln!("got: {:?}\n", data);
|
||||
// parentfd.shutdown(std::net::Shutdown::Write)?;
|
||||
let msg = serde_json::from_slice(&data)
|
||||
.map_err(|e| format!("unable to read worker process response: {}", e))?;
|
||||
let sub_task = match msg {
|
||||
SessionChildToParent::Error { error } => return Err(error.into()),
|
||||
SessionChildToParent::FinalChildPid(raw_pid) => Pid::from_raw(raw_pid as i32),
|
||||
_ => panic!("unexpected message"),
|
||||
};
|
||||
|
||||
Ok(SessionChild {
|
||||
service: self.service.to_string(),
|
||||
opened: Instant::now(),
|
||||
task: child,
|
||||
task: self.task,
|
||||
sub_task,
|
||||
})
|
||||
}
|
||||
|
Reference in New Issue
Block a user