Use stdin as VT for current/none vt selections
The controlling tty will now be obtained from stdin if possible for current/non, which is useful for inittab setups and shell test use-cases.
This commit is contained in:
@@ -10,7 +10,7 @@ use crate::{
|
||||
error::Error,
|
||||
session::{
|
||||
interface::{Session, SessionChild, SessionState},
|
||||
worker::AuthMessageType as SessAuthMessageType,
|
||||
worker::{AuthMessageType as SessAuthMessageType, TerminalMode},
|
||||
},
|
||||
};
|
||||
use greetd_ipc::AuthMessageType;
|
||||
@@ -37,16 +37,16 @@ pub struct Context {
|
||||
inner: RwLock<ContextInner>,
|
||||
greeter_bin: String,
|
||||
greeter_user: String,
|
||||
vt: usize,
|
||||
pam_service: String,
|
||||
term_mode: TerminalMode,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new(
|
||||
greeter_bin: String,
|
||||
greeter_user: String,
|
||||
vt: usize,
|
||||
pam_service: String,
|
||||
term_mode: TerminalMode,
|
||||
) -> Context {
|
||||
Context {
|
||||
inner: RwLock::new(ContextInner {
|
||||
@@ -56,8 +56,8 @@ impl Context {
|
||||
}),
|
||||
greeter_bin,
|
||||
greeter_user,
|
||||
vt,
|
||||
pam_service,
|
||||
term_mode,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ impl Context {
|
||||
) -> Result<SessionChild, Error> {
|
||||
let mut scheduled_session = Session::new_external()?;
|
||||
scheduled_session
|
||||
.initiate(&self.pam_service, class, user, false, self.vt)
|
||||
.initiate(&self.pam_service, class, user, false, &self.term_mode)
|
||||
.await?;
|
||||
loop {
|
||||
match scheduled_session.get_state().await {
|
||||
@@ -157,7 +157,7 @@ impl Context {
|
||||
};
|
||||
session_set
|
||||
.session
|
||||
.initiate(&self.pam_service, "user", &username, true, self.vt)
|
||||
.initiate(&self.pam_service, "user", &username, true, &self.term_mode)
|
||||
.await?;
|
||||
|
||||
let mut session = Some(session_set);
|
||||
@@ -305,9 +305,7 @@ impl Context {
|
||||
drop(inner);
|
||||
let s = match scheduled.session.start().await {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return Err(format!("session start failed: {}", e).into());
|
||||
}
|
||||
Err(e) => return Err(format!("session start failed: {}", e).into()),
|
||||
};
|
||||
let mut inner = self.inner.write().await;
|
||||
inner.current = Some(SessionChildSet {
|
||||
|
@@ -11,6 +11,7 @@ use crate::{
|
||||
config::{Config, VtSelection},
|
||||
context::Context,
|
||||
error::Error,
|
||||
session::worker::TerminalMode,
|
||||
terminal::{self, Terminal},
|
||||
};
|
||||
use greetd_ipc::{
|
||||
@@ -18,10 +19,15 @@ use greetd_ipc::{
|
||||
ErrorType, Request, Response,
|
||||
};
|
||||
|
||||
fn reset_vt(vt: usize) -> Result<(), Error> {
|
||||
let term = Terminal::open(vt)?;
|
||||
term.kd_setmode(terminal::KdMode::Text)?;
|
||||
term.vt_setactivate(vt)?;
|
||||
fn reset_vt(term_mode: &TerminalMode) -> Result<(), Error> {
|
||||
match term_mode {
|
||||
TerminalMode::Terminal { path, vt, .. } => {
|
||||
let term = Terminal::open(path)?;
|
||||
term.kd_setmode(terminal::KdMode::Text)?;
|
||||
term.vt_setactivate(*vt)?;
|
||||
}
|
||||
TerminalMode::Stdin => (),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -76,6 +82,84 @@ async fn client_handler(ctx: &Context, mut s: UnixStream) -> Result<(), Error> {
|
||||
}
|
||||
}
|
||||
|
||||
// Return a TTY path and the TTY/VT number, based on the configured target.
|
||||
//
|
||||
// If the target is VtSelection::Current, return the path to the TTY
|
||||
// referenced by stdin and the TTY number it is connected to if possible. If
|
||||
// the referenced TTY is a PTY, fail. Otherwise, open tty0, get the current VT
|
||||
// number, and return the path to that TTY and VT.
|
||||
//
|
||||
// If the target is VtSelection::Next, open tty0 and request the next VT
|
||||
// number. Return the TTY and VT
|
||||
//
|
||||
// If the target is VtSelection::Specific, simply return the specified TTY and
|
||||
// VT.
|
||||
//
|
||||
// If the target is VtSelection::None, return nothing.
|
||||
fn get_tty(config: &Config) -> Result<TerminalMode, Error> {
|
||||
const TTY_PREFIX: &str = "/dev/tty";
|
||||
const PTS_PREFIX: &str = "/dev/pts";
|
||||
|
||||
let term = match config.file.terminal.vt {
|
||||
VtSelection::Current => {
|
||||
let term = Terminal::stdin();
|
||||
match term.ttyname() {
|
||||
// We have a usable terminal, so let's decipher and return that
|
||||
Ok(term_name)
|
||||
if term_name.starts_with(TTY_PREFIX) && term_name.len() > TTY_PREFIX.len() =>
|
||||
{
|
||||
let vt = term_name[TTY_PREFIX.len()..]
|
||||
.parse()
|
||||
.map_err(|e| Error::Error(format!("unable to parse tty number: {}", e)))?;
|
||||
|
||||
TerminalMode::Terminal {
|
||||
path: term_name,
|
||||
vt,
|
||||
switch: false,
|
||||
}
|
||||
}
|
||||
Ok(term_name) if term_name.starts_with(PTS_PREFIX) => {
|
||||
return Err("cannot use current VT when started from a psuedo terminal".into())
|
||||
}
|
||||
// We don't have a usable terminal, so we have to jump through some hoops
|
||||
_ => {
|
||||
let sys_term = Terminal::open("/dev/tty0")
|
||||
.map_err(|e| format!("unable to open terminal: {}", e))?;
|
||||
let vt = sys_term
|
||||
.vt_get_current()
|
||||
.map_err(|e| format!("unable to get current VT: {}", e))?;
|
||||
TerminalMode::Terminal {
|
||||
path: format!("/dev/tty{}", vt),
|
||||
vt,
|
||||
switch: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
VtSelection::Next => {
|
||||
let term = Terminal::open("/dev/tty0")
|
||||
.map_err(|e| format!("unable to open terminal: {}", e))?;
|
||||
let vt = term
|
||||
.vt_get_next()
|
||||
.map_err(|e| format!("unable to get next VT: {}", e))?;
|
||||
TerminalMode::Terminal {
|
||||
path: format!("/dev/tty{}", vt),
|
||||
vt,
|
||||
switch: true,
|
||||
}
|
||||
}
|
||||
VtSelection::None => TerminalMode::Stdin,
|
||||
VtSelection::Specific(vt) => TerminalMode::Terminal {
|
||||
path: format!("/dev/tty{}", vt),
|
||||
vt,
|
||||
switch: true,
|
||||
},
|
||||
};
|
||||
return Ok(term);
|
||||
}
|
||||
|
||||
// Listener is a convenience wrapper for creating the UnixListener we need, and
|
||||
// for providing cleanup on Drop.
|
||||
struct Listener(UnixListener);
|
||||
|
||||
impl Listener {
|
||||
@@ -123,36 +207,25 @@ pub async fn main(config: Config) -> Result<(), Error> {
|
||||
|
||||
let mut listener = Listener::create(uid, gid)?;
|
||||
|
||||
let term = Terminal::open(0).map_err(|e| format!("unable to open terminal: {}", e))?;
|
||||
let vt = match config.file.terminal.vt {
|
||||
VtSelection::Current => term
|
||||
.vt_get_current()
|
||||
.map_err(|e| format!("unable to get current VT: {}", e))?,
|
||||
VtSelection::Next => term
|
||||
.vt_get_next()
|
||||
.map_err(|e| format!("unable to get next VT: {}", e))?,
|
||||
VtSelection::None => 0,
|
||||
VtSelection::Specific(v) => v,
|
||||
};
|
||||
drop(term);
|
||||
let term_mode = get_tty(&config)?;
|
||||
|
||||
let ctx = Rc::new(Context::new(
|
||||
config.file.default_session.command,
|
||||
config.file.default_session.user,
|
||||
vt,
|
||||
service.to_string(),
|
||||
term_mode.clone(),
|
||||
));
|
||||
|
||||
if let Some(s) = config.file.initial_session {
|
||||
if let Err(e) = ctx.start_user_session(&s.user, vec![s.command]).await {
|
||||
eprintln!("unable to start greeter: {}", e);
|
||||
reset_vt(vt).map_err(|e| format!("unable to reset VT: {}", e))?;
|
||||
reset_vt(&term_mode).map_err(|e| format!("unable to reset VT: {}", e))?;
|
||||
|
||||
std::process::exit(1);
|
||||
}
|
||||
} else if let Err(e) = ctx.greet().await {
|
||||
eprintln!("unable to start greeter: {}", e);
|
||||
reset_vt(vt).map_err(|e| format!("unable to reset VT: {}", e))?;
|
||||
reset_vt(&term_mode).map_err(|e| format!("unable to reset VT: {}", e))?;
|
||||
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@ use async_trait::async_trait;
|
||||
|
||||
use tokio::net::UnixDatagram as TokioUnixDatagram;
|
||||
|
||||
use super::worker::{AuthMessageType, ParentToSessionChild, SessionChildToParent};
|
||||
use super::worker::{AuthMessageType, ParentToSessionChild, SessionChildToParent, TerminalMode};
|
||||
use crate::error::Error;
|
||||
|
||||
#[async_trait]
|
||||
@@ -54,14 +54,14 @@ impl AsyncRecv<SessionChildToParent> for SessionChildToParent {
|
||||
|
||||
/// SessionChild tracks the processes spawned by a session
|
||||
pub struct SessionChild {
|
||||
task: Pid,
|
||||
sub_task: Pid,
|
||||
pub task: Pid,
|
||||
pub sub_task: Pid,
|
||||
}
|
||||
|
||||
impl SessionChild {
|
||||
/// Check if this session has this pid.
|
||||
pub fn owns_pid(&self, pid: Pid) -> bool {
|
||||
self.task == pid || self.sub_task == pid
|
||||
self.task == pid
|
||||
}
|
||||
|
||||
/// Send SIGTERM to the session child.
|
||||
@@ -136,14 +136,14 @@ impl Session {
|
||||
class: &str,
|
||||
user: &str,
|
||||
authenticate: bool,
|
||||
vt: usize,
|
||||
term_mode: &TerminalMode,
|
||||
) -> Result<(), Error> {
|
||||
let msg = ParentToSessionChild::InitiateLogin {
|
||||
service: service.to_string(),
|
||||
class: class.to_string(),
|
||||
user: user.to_string(),
|
||||
authenticate,
|
||||
vt,
|
||||
tty: term_mode.clone(),
|
||||
};
|
||||
msg.send(&mut self.sock).await?;
|
||||
Ok(())
|
||||
|
@@ -22,6 +22,16 @@ pub enum AuthMessageType {
|
||||
Error,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum TerminalMode {
|
||||
Terminal {
|
||||
path: String,
|
||||
vt: usize,
|
||||
switch: bool,
|
||||
},
|
||||
Stdin,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum ParentToSessionChild {
|
||||
InitiateLogin {
|
||||
@@ -29,7 +39,7 @@ pub enum ParentToSessionChild {
|
||||
class: String,
|
||||
user: String,
|
||||
authenticate: bool,
|
||||
vt: usize,
|
||||
tty: TerminalMode,
|
||||
},
|
||||
PamResponse {
|
||||
resp: Option<String>,
|
||||
@@ -70,14 +80,14 @@ impl SessionChildToParent {
|
||||
/// responsible for the entirety of the session setup and execution. It is
|
||||
/// started by Session::start.
|
||||
fn worker(sock: &UnixDatagram) -> Result<(), Error> {
|
||||
let (service, class, user, authenticate, vt) = match ParentToSessionChild::recv(sock)? {
|
||||
let (service, class, user, authenticate, tty) = match ParentToSessionChild::recv(sock)? {
|
||||
ParentToSessionChild::InitiateLogin {
|
||||
service,
|
||||
class,
|
||||
user,
|
||||
authenticate,
|
||||
vt,
|
||||
} => (service, class, user, authenticate, vt),
|
||||
tty,
|
||||
} => (service, class, user, authenticate, tty),
|
||||
ParentToSessionChild::Cancel => return Err("cancelled".into()),
|
||||
msg => return Err(format!("expected InitiateLogin or Cancel, got: {:?}", msg).into()),
|
||||
};
|
||||
@@ -85,9 +95,6 @@ fn worker(sock: &UnixDatagram) -> Result<(), Error> {
|
||||
let conv = Box::pin(SessionConv::new(sock));
|
||||
let mut pam = PamSession::start(&service, &user, conv)?;
|
||||
|
||||
// Tell PAM what TTY we're targetting, which is used by logind.
|
||||
pam.set_item(PamItemType::TTY, &format!("tty{}", vt))?;
|
||||
|
||||
if authenticate {
|
||||
pam.authenticate(PamFlag::NONE)?;
|
||||
}
|
||||
@@ -122,33 +129,37 @@ fn worker(sock: &UnixDatagram) -> Result<(), Error> {
|
||||
// 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 target_term = terminal::Terminal::open(vt)?;
|
||||
match tty {
|
||||
TerminalMode::Stdin => (),
|
||||
TerminalMode::Terminal { path, vt, switch } => {
|
||||
// Tell PAM what TTY we're targetting, which is used by logind.
|
||||
pam.set_item(PamItemType::TTY, &format!("tty{}", vt))?;
|
||||
pam.putenv(&format!("XDG_VTNR={}", vt))?;
|
||||
|
||||
// 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)?;
|
||||
// Opening our target terminal.
|
||||
let target_term = terminal::Terminal::open(&path)?;
|
||||
|
||||
// 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 != 0 && vt != target_term.vt_get_current()? {
|
||||
// Perform a switch to the target VT, simultaneously resetting it to
|
||||
// VT_AUTO.
|
||||
target_term.vt_setactivate(vt)?;
|
||||
// Clear TTY so that it will be empty when we switch to it.
|
||||
target_term.term_clear()?;
|
||||
|
||||
// A bit more work if a VT switch is required.
|
||||
if switch && vt != target_term.vt_get_current()? {
|
||||
// Perform a switch to the target VT, simultaneously resetting it to
|
||||
// VT_AUTO.
|
||||
target_term.vt_setactivate(vt)?;
|
||||
}
|
||||
|
||||
// Connect std(in|out|err), and make this our controlling TTY.
|
||||
target_term.term_connect_pipes()?;
|
||||
target_term.term_take_ctty()?;
|
||||
}
|
||||
}
|
||||
|
||||
// Connect std(in|out|err), and make this our controlling TTY.
|
||||
target_term.term_connect_pipes()?;
|
||||
target_term.term_take_ctty()?;
|
||||
|
||||
// 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("");
|
||||
@@ -174,7 +185,6 @@ fn worker(sock: &UnixDatagram) -> Result<(), Error> {
|
||||
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),
|
||||
|
@@ -6,7 +6,7 @@ use nix::{
|
||||
sys::stat::Mode,
|
||||
unistd::{close, dup2, write},
|
||||
};
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::{ffi::CStr, os::unix::io::RawFd};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub enum KdMode {
|
||||
@@ -25,28 +25,67 @@ impl KdMode {
|
||||
|
||||
pub struct Terminal {
|
||||
fd: RawFd,
|
||||
autoclose: bool,
|
||||
}
|
||||
|
||||
impl Drop for Terminal {
|
||||
fn drop(&mut self) {
|
||||
close(self.fd).unwrap();
|
||||
if self.autoclose {
|
||||
close(self.fd).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ttyname_r(fd: RawFd) -> Result<String, Error> {
|
||||
let mut arr: [u8; 32] = [0; 32];
|
||||
let res = unsafe {
|
||||
libc::ttyname_r(
|
||||
fd as libc::c_int,
|
||||
&mut arr[0] as *mut u8 as *mut libc::c_char,
|
||||
31,
|
||||
)
|
||||
};
|
||||
if res != 0 {
|
||||
return Err("ttyname_r failed".into());
|
||||
}
|
||||
let len = unsafe { libc::strnlen(&arr[0] as *const u8 as *const libc::c_char, 31) };
|
||||
let s = CStr::from_bytes_with_nul(&arr[..len + 1])
|
||||
.map_err(|e| Error::Error(format!("ttyname_r result conversion failed: {}", e)))?;
|
||||
Ok(s.to_str()
|
||||
.map_err(|e| Error::Error(format!("ttyname_r result conversion failed: {}", e)))?
|
||||
.to_string())
|
||||
}
|
||||
|
||||
impl Terminal {
|
||||
/// Open the terminal file for the specified terminal number.
|
||||
pub fn open(terminal: usize) -> Result<Terminal, Error> {
|
||||
pub fn open(terminal: &str) -> Result<Terminal, Error> {
|
||||
let res = open(
|
||||
format!("/dev/tty{}", terminal).as_str(),
|
||||
terminal,
|
||||
OFlag::O_RDWR | OFlag::O_NOCTTY,
|
||||
Mode::from_bits_truncate(0o666),
|
||||
);
|
||||
match res {
|
||||
Ok(fd) => Ok(Terminal { fd }),
|
||||
Err(e) => Err(format!("terminal: unable to open: {}", e).into()),
|
||||
Ok(fd) => Ok(Terminal {
|
||||
fd,
|
||||
autoclose: true,
|
||||
}),
|
||||
Err(e) => return Err(format!("terminal: unable to open: {}", e).into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Open the terminal from stdin
|
||||
pub fn stdin() -> Terminal {
|
||||
Terminal {
|
||||
fd: 0 as RawFd,
|
||||
autoclose: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the name of the TTY
|
||||
pub fn ttyname(&self) -> Result<String, Error> {
|
||||
ttyname_r(self.fd)
|
||||
}
|
||||
|
||||
/// Set the kernel display to either graphics or text mode. Graphivs mode
|
||||
/// disables the kernel console on this VT, and also disables blanking
|
||||
/// between VT switches if both source and target VT is in graphics mode.
|
||||
|
Reference in New Issue
Block a user