commit 2571da3772ff3219af1ccfce169bcaab5603501f Author: Kenny Levinsen Date: Tue Sep 10 00:36:35 2019 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53eaa21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +**/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1d365af --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,140 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "bitflags" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "byteorder" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cc" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "greetctl" +version = "0.1.0" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rpassword 4.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "greetd" +version = "0.1.0" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pam 0.7.0 (git+https://github.com/regiontog/pam.git)", + "users 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "nix" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pam" +version = "0.7.0" +source = "git+https://github.com/regiontog/pam.git#efd6bdc2ba0b07ed932de47fcdad5fd6e40cc3c8" +dependencies = [ + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "pam-sys 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "users 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pam-sys" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rpassword" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "users" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "users" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" +"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" +"checksum cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)" = "4fc9a35e1f4290eb9e5fc54ba6cf40671ed2a2514c3eeb2b2a908dda2ea5a1be" +"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" +"checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" +"checksum nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3b2e0b4f3320ed72aaedb9a5ac838690a8047c7b275da22711fddff4f8a14229" +"checksum pam 0.7.0 (git+https://github.com/regiontog/pam.git)" = "" +"checksum pam-sys 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "cd4858311a097f01a0006ef7d0cd50bca81ec430c949d7bf95cbefd202282434" +"checksum rpassword 4.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f072d931f11a96546efd97642e1e75e807345aced86b947f9239102f262d0fcd" +"checksum users 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7fed7d0912567d35f88010c23dbaf865e9da8b5227295e8dc0f2fdd109155ab7" +"checksum users 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c72f4267aea0c3ec6d07eaabea6ead7c5ddacfafc5e22bcf8d186706851fb4cf" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..5c2d256 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["greetd", "greetctl"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..97e4164 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# greetd + +Generic display manager. Composed of a daemon which: + +1. Launches a greeter of your choice. +2. Listens on a socket for a login message. +3. If the credentials are valid, terminates the greeter and starts the requested session application. +4. When the session application terminates, the greeter is started once again. + +All the greeter of choice needs to do is to be able to write a message to a socket. It could be anything from a simple terminal application to a fully-fledged desktop environment in which one of the applications present a user prompt. + +The greeter runs as a configured user, which is supposed to be one with no interesting privileges except for what the greeter itself needs to run. + +Future plans involve adding lock-screen support. + +## Included in the box: + +1. greetd, the daemon itself +2. greetctl, a sample application to issue the login message. + +## It doesn't work yet! + +Why are you using this yet? I haven't even tested it myself! \ No newline at end of file diff --git a/greetctl/Cargo.toml b/greetctl/Cargo.toml new file mode 100644 index 0000000..26e99ea --- /dev/null +++ b/greetctl/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "greetctl" +version = "0.1.0" +authors = ["Kenny Levinsen "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +byteorder = "1.3" +rpassword = "4.0" \ No newline at end of file diff --git a/greetctl/src/main.rs b/greetctl/src/main.rs new file mode 100644 index 0000000..c166025 --- /dev/null +++ b/greetctl/src/main.rs @@ -0,0 +1,36 @@ +use std::io::{self, Write, BufRead}; +use std::env; +use std::os::unix::net::UnixStream; + +use rpassword::prompt_password_stderr; +use byteorder::{LittleEndian, WriteBytesExt}; + +fn prompt_username_stderr(prompt: &str) -> Result> { + let stdin = io::stdin(); + let mut stdin_iter = stdin.lock().lines(); + eprint!("{}", prompt); + Ok(stdin_iter.next().unwrap()?) +} + +fn login(username: String, password: String) -> Result<(), Box> { + let msg_len = username.len() + password.len() + 8; + let mut buf = Vec::with_capacity(msg_len + 16); + buf.write_u32::(0xAFBFCFDF)?; // Proto Magic + buf.write_u32::(1)?; // Proto version + buf.write_u32::(1)?; // Message type + buf.write_u32::(msg_len as u32)?; // Payload length + buf.write_u32::(username.len() as u32)?; + buf.extend(username.into_bytes()); + buf.write_u32::(password.len() as u32)?; + buf.extend(password.into_bytes()); + + let mut stream = UnixStream::connect(env::var("GREETD_SOCK")?)?; + stream.write_all(&buf)?; + Ok(()) +} + +fn main() { + let username = prompt_username_stderr("Username: ").unwrap(); + let password = prompt_password_stderr("Password: ").unwrap(); + login(username, password).unwrap(); +} diff --git a/greetd/.gitignore b/greetd/.gitignore new file mode 100644 index 0000000..53eaa21 --- /dev/null +++ b/greetd/.gitignore @@ -0,0 +1,2 @@ +/target +**/*.rs.bk diff --git a/greetd/Cargo.toml b/greetd/Cargo.toml new file mode 100644 index 0000000..efbc41f --- /dev/null +++ b/greetd/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "greetd" +version = "0.1.0" +authors = ["Kenny Levinsen "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nix = "0.15" +pam = { git = "https://github.com/regiontog/pam.git" } +users = "0.9.1" +byteorder = "1.3" diff --git a/greetd/src/client.rs b/greetd/src/client.rs new file mode 100644 index 0000000..067b563 --- /dev/null +++ b/greetd/src/client.rs @@ -0,0 +1,165 @@ +use std::error::Error; +use std::io; +use std::io::{Read, Take}; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::os::unix::net::UnixStream; + +use nix::poll::PollFlags; + +use byteorder::{LittleEndian, ReadBytesExt}; + +use crate::context::Context; +use crate::pollable::{PollRunResult, Pollable}; +use crate::scrambler::Scrambler; + +enum ClientState { + AwaitingHeader, + AwaitingPayload { typ: u32, len: u32 }, +} + +const IPC_HEADERLEN: usize = 16; // 4 bytes magic, 4 bytes version, 4 byte type, 4 byte len + +pub struct Client { + stream: Take, + buf: Vec, + state: ClientState, +} + +impl Client { + fn read_header(cursor: &mut std::io::Cursor<&[u8]>) -> Result<(u32, u32), Box> { + let proto_magic = cursor.read_u32::()?; + if proto_magic != 0xAFBFCFDF { + return Err(io::Error::new(io::ErrorKind::Other, "invalid message magic").into()); + } + + let proto_version = cursor.read_u32::()?; + if proto_version != 1 { + return Err(io::Error::new(io::ErrorKind::Other, "invalid message version").into()); + } + + let msg_type = cursor.read_u32::()?; + let msg_len = cursor.read_u32::()?; + Ok((msg_type, msg_len)) + } + + fn read_string(cursor: &mut std::io::Cursor<&[u8]>) -> Result> { + let len = cursor.read_u32::()?; + let mut data: Vec = vec![0; len as usize]; + cursor.read_exact(&mut data)?; + String::from_utf8(data).map_err(|x| x.into()) + } + + fn read_login( + cursor: &mut std::io::Cursor<&[u8]>, + ) -> Result<(String, String, String), Box> { + let user = Client::read_string(cursor)?; + let pass = Client::read_string(cursor)?; + let cmd = Client::read_string(cursor)?; + Ok((user, pass, cmd)) + } + + pub fn new(stream: UnixStream) -> Result> { + stream.set_nonblocking(true)?; + Ok(Client { + stream: stream.take(IPC_HEADERLEN as u64), + buf: Vec::new(), + state: ClientState::AwaitingHeader, + }) + } +} + +impl Pollable for Client { + fn fd(&self) -> RawFd { + self.stream.get_ref().as_raw_fd() + } + + fn poll_flags(&self) -> PollFlags { + PollFlags::POLLIN + } + + fn run(&mut self, ctx: &mut Context) -> Result> { + loop { + match self.state { + ClientState::AwaitingHeader => { + match self.stream.read_to_end(&mut self.buf) { + Ok(_) => { + if self.buf.len() < IPC_HEADERLEN { + // Got EOF before we got enough data. + self.buf.scramble(); + break Ok(PollRunResult::Dead); + } + let mut rdr = std::io::Cursor::new(self.buf.as_slice()); + let (msg_type, msg_len) = match Client::read_header(&mut rdr) { + Ok(v) => v, + Err(_) => { + self.buf.scramble(); + break Ok(PollRunResult::Dead); + } + }; + + self.state = ClientState::AwaitingPayload { + typ: msg_type, + len: msg_len, + }; + self.stream.set_limit(msg_len as u64); + self.buf.truncate(0); + } + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { + break Ok(PollRunResult::Uneventful) + } + Err(_) => { + self.buf.scramble(); + break Ok(PollRunResult::Dead); + } + } + } + ClientState::AwaitingPayload { typ, len } => { + match self.stream.read_to_end(&mut self.buf) { + Ok(_) => { + if self.buf.len() < len as usize { + // Got EOF before we got enough data. + self.buf.scramble(); + break Ok(PollRunResult::Dead); + } + self.state = ClientState::AwaitingHeader; + self.stream.set_limit(IPC_HEADERLEN as u64); + match typ { + 1 => { + // Login + let mut rdr = std::io::Cursor::new(self.buf.as_slice()); + let (user, pass, cmd) = match Client::read_login(&mut rdr) { + Ok(v) => v, + Err(_) => { + self.buf.scramble(); + break Ok(PollRunResult::Dead); + } + }; + self.buf.scramble(); + + ctx.login(user, pass, cmd)?; + } + 2 => { + // Screen lock + self.buf.scramble(); + unimplemented!("screen lock has not yet been implemented"); + } + _ => { + // Unknown message type + self.buf.scramble(); + break Ok(PollRunResult::Dead); + } + } + } + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { + break Ok(PollRunResult::Uneventful) + } + Err(_) => { + self.buf.scramble(); + break Ok(PollRunResult::Dead); + } + } + } + } + } + } +} diff --git a/greetd/src/context.rs b/greetd/src/context.rs new file mode 100644 index 0000000..a818042 --- /dev/null +++ b/greetd/src/context.rs @@ -0,0 +1,208 @@ +use std::env; +use std::error::Error; +use std::ffi::CString; +use std::io; + +use nix::errno::Errno; +use nix::sys::signal::Signal; +use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus}; +use nix::unistd::{alarm, execv, execve, fork, initgroups, setgid, setuid, ForkResult, Gid, Uid}; + +use users::os::unix::UserExt; + +use crate::scrambler::Scrambler; + +struct Session<'a> { + pam: pam::Authenticator<'a, pam::PasswordConv>, + task: nix::unistd::Pid, +} + +struct Greeter { + task: nix::unistd::Pid, +} + +pub struct Context<'a> { + session: Option>, + greeter: Option, + + greeter_bin: String, + greeter_user: String, + tty: u32, +} + +impl<'a> Context<'a> { + pub fn new(greeter_bin: String, greeter_user: String, tty: u32) -> Context<'a> { + Context { + session: None, + greeter: None, + greeter_bin: greeter_bin, + greeter_user: greeter_user, + tty: tty, + } + } + + pub fn greet(&mut self) -> Result<(), Box> { + let u = users::get_user_by_name(&self.greeter_user).expect("unable to get user struct"); + + let uid = Uid::from_raw(u.uid()); + let gid = Gid::from_raw(u.primary_group_id()); + + let cusername = CString::new(u.name().to_str().expect("unable to get username")) + .expect("unable to create username CString"); + let cpath = CString::new("/bin/sh").unwrap(); + let cargs = [ + CString::new("/bin/sh").unwrap(), + CString::new("-c").unwrap(), + CString::new(format!("[ -f /etc/profile ] && source /etc/profile; [ -f $HOME/.profile ] && source $HOME/.profile; exec {}", self.greeter_bin)).unwrap() + ]; + + let child = match fork()? { + ForkResult::Parent { child, .. } => child, + ForkResult::Child => { + initgroups(&cusername, gid).expect("unable to init groups"); + setgid(gid).expect("unable to set GID"); + setuid(uid).expect("unable to set UID"); + env::set_current_dir(&u.home_dir()).expect("unable to set current directory"); + execv(&cpath, &cargs).unwrap(); + unreachable!("after exec"); + } + }; + + self.greeter = Some(Greeter { task: child }); + + Ok(()) + } + + pub fn login( + &mut self, + mut username: String, + mut password: String, + cmd: String, + ) -> Result<(), Box> { + if self.session.is_some() { + return Err(io::Error::new(io::ErrorKind::Other, "session already active").into()); + } + + let mut auth = + pam::Authenticator::with_password("login").expect("unable to create pam authenticator"); + auth.handler_mut() + .set_credentials(username.as_str(), password.as_str()) + .expect("unable to set credentials"); + if !auth.authenticate().is_ok() { + return Err(io::Error::new(io::ErrorKind::Other, "authentication failed").into()); + } + + password.scramble(); + + let u = users::get_user_by_name(&username).expect("unable to get user struct"); + username.scramble(); + + let uid = Uid::from_raw(u.uid()); + let gid = Gid::from_raw(u.primary_group_id()); + + let cusername = CString::new(u.name().to_str().expect("unable to get username")) + .expect("unable to create username CString"); + let cpath = CString::new("/bin/sh").unwrap(); + let cargs = [ + cpath.clone(), + CString::new("-c").unwrap(), + CString::new(format!("[ -f /etc/profile ] && source /etc/profile; [ -f $HOME/.profile ] && source $HOME/.profile; exec {}", cmd)).unwrap() + ]; + + auth.env("XDG_SESSION_CLASS", "user")?; + auth.env("XDG_VTNR", self.tty.to_string())?; + auth.env("XDG_SEAT", "seat0")?; + + auth.open_session().expect("unable to open session"); + + let env: Vec = if let Some(pamenv) = auth.environment() { + env::vars() + .map(|(x, y)| format!("{}={}", x, y)) + .chain(pamenv.iter().map(|x| x.to_string_lossy().into_owned())) + .map(|x| CString::new(x).unwrap()) + .collect() + } else { + env::vars() + .map(|(x, y)| format!("{}={}", x, y)) + .map(|x| CString::new(x).unwrap()) + .collect() + }; + + match self.greeter.take() { + Some(greeter) => { + let _ = nix::sys::signal::kill(greeter.task, Signal::SIGTERM); + alarm::set(1); + match waitpid(greeter.task, None) { + Ok(_) => (), + Err(nix::Error::Sys(Errno::EINTR)) => { + let _ = nix::sys::signal::kill(greeter.task, Signal::SIGKILL); + } + Err(_) => (), // ??? + } + alarm::cancel(); + } + None => (), + }; + + let child = match fork()? { + ForkResult::Parent { child, .. } => child, + ForkResult::Child => { + initgroups(&cusername, gid).expect("unable to init groups"); + setgid(gid).expect("unable to set GID"); + setuid(uid).expect("unable to set UID"); + env::set_current_dir(&u.home_dir()).expect("unable to set current directory"); + execve(&cpath, &cargs, &env).unwrap(); + unreachable!("after exec"); + } + }; + + self.session = Some(Session { + task: child, + pam: auth, + }); + + Ok(()) + } + + pub fn check_children(&mut self) { + match self.session.take() { + Some(session) => { + match waitpid(session.task, Some(WaitPidFlag::WNOHANG)) { + Ok(WaitStatus::Exited(..)) | Ok(WaitStatus::Signaled(..)) => { + // Session task is dead, so kill the session and + // restart the greeter. + drop(session.pam); + self.greet().expect("unable to start greeter"); + } + Ok(WaitStatus::StillAlive) => self.session = Some(session), + _ => panic!("waitpid on session returned unexpected status"), + } + } + None => (), + }; + match self.greeter.take() { + Some(greeter) => { + match waitpid(greeter.task, Some(WaitPidFlag::WNOHANG)) { + Ok(WaitStatus::Exited(..)) | Ok(WaitStatus::Signaled(..)) => { + if self.session.is_none() { + // Greeter died on us, let's just die with it. + std::process::exit(1); + } + } + Ok(WaitStatus::StillAlive) => self.greeter = Some(greeter), + _ => panic!("waitpid on greeter returned unexpected status"), + } + } + None => (), + }; + } + + pub fn terminate(&mut self) { + if let Some(greeter) = &self.greeter { + let _ = nix::sys::signal::kill(greeter.task, Signal::SIGTERM); + } + if let Some(session) = &self.session { + let _ = nix::sys::signal::kill(session.task, Signal::SIGTERM); + } + } +} diff --git a/greetd/src/listener.rs b/greetd/src/listener.rs new file mode 100644 index 0000000..ec370db --- /dev/null +++ b/greetd/src/listener.rs @@ -0,0 +1,44 @@ +use std::cell::RefCell; +use std::error::Error; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::os::unix::net::UnixListener; +use std::rc::Rc; + +use nix::poll::PollFlags; + +use crate::client::Client; +use crate::context::Context; +use crate::pollable::{PollRunResult, Pollable}; + +pub struct Listener { + listener: UnixListener, +} + +impl Listener { + pub fn new(p: &str) -> Result> { + let listener = UnixListener::bind(p)?; + listener.set_nonblocking(true)?; + Ok(Listener { listener }) + } +} + +impl Pollable for Listener { + fn fd(&self) -> RawFd { + self.listener.as_raw_fd() + } + + fn poll_flags(&self) -> PollFlags { + PollFlags::POLLIN + } + + fn run(&mut self, _: &mut Context) -> Result> { + let stream = match self.listener.accept() { + Ok((stream, _)) => stream, + Err(_) => return Ok(PollRunResult::Uneventful), + }; + + Ok(PollRunResult::NewPollable(Rc::new(RefCell::new(Box::new( + Client::new(stream)?, + ))))) + } +} diff --git a/greetd/src/main.rs b/greetd/src/main.rs new file mode 100644 index 0000000..0190732 --- /dev/null +++ b/greetd/src/main.rs @@ -0,0 +1,121 @@ +use std::cell::RefCell; +use std::env; +use std::os::unix::io::AsRawFd; +use std::rc::Rc; + +use nix::ioctl_write_int_bad; +use nix::poll::{poll, PollFd}; +use nix::unistd::chown; + +mod client; +mod context; +mod listener; +mod pollable; +mod scrambler; +mod signals; + +use crate::context::Context; +use crate::listener::Listener; +use crate::pollable::{PollRunResult, Pollable}; +use crate::signals::Signals; + +ioctl_write_int_bad!(vt_activate, 0x5606); +ioctl_write_int_bad!(vt_waitactive, 0x5607); + +const GREETD_SOCK: &'static str = "/run/greetd.sock"; + +fn main() { + let tty = env::var("GREETD_TTY") + .expect("unable to get tty") + .parse() + .expect("unable to parse tty"); + + let greeter_user = env::var("GREETD_GREETER_USER").expect("unable to get greeter user"); + + let greeter_bin = env::var("GREETD_GREETER").expect("unable to get greeter"); + + env::set_var("GREETD_SOCK", GREETD_SOCK); + + let listener = Listener::new(GREETD_SOCK).expect("unable to create listener"); + + let u = users::get_user_by_name(&greeter_user).expect("unable to get user struct"); + let uid = nix::unistd::Uid::from_raw(u.uid()); + let gid = nix::unistd::Gid::from_raw(u.primary_group_id()); + chown(GREETD_SOCK, Some(uid), Some(gid)).expect("unable to chown greetd socket"); + + let signals = Signals::new().expect("unable to create signalfd"); + + let file = std::fs::OpenOptions::new() + .write(true) + .read(false) + .open("/dev/console") + .expect("unable to open console"); + + unsafe { + vt_activate(file.as_raw_fd(), tty as i32).expect("unable to activate"); + vt_waitactive(file.as_raw_fd(), tty as i32).expect("unable to wait for activation"); + } + drop(file); + + let mut ctx = Context::new(greeter_bin, greeter_user, tty); + ctx.greet().expect("unable to start greeter"); + + let mut pollables: Vec>>> = vec![ + Rc::new(RefCell::new(Box::new(listener))), + Rc::new(RefCell::new(Box::new(signals))), + ]; + + let mut fds: Vec = pollables + .iter() + .map(|x| PollFd::new(x.borrow().fd(), x.borrow().poll_flags())) + .collect(); + + let mut new_pollables: Vec>>> = Vec::new(); + let mut dead_pollables: Vec = Vec::new(); + + loop { + poll(&mut fds, -1).expect("poll failed"); + + for (idx, fd) in fds.iter().enumerate() { + if let Some(revents) = fd.revents() { + let pollable = &pollables[idx]; + if revents.intersects(pollable.borrow().poll_flags()) { + match pollable + .borrow_mut() + .run(&mut ctx) + .expect("pollable run failed") + { + PollRunResult::Uneventful => (), + PollRunResult::NewPollable(p) => new_pollables.push(p), + PollRunResult::Dead => dead_pollables.push(idx), + } + } + } + } + + let fds_changed = dead_pollables.len() > 0 || new_pollables.len() > 0; + + if dead_pollables.len() > 0 { + let mut removed = 0; + for dead in dead_pollables.into_iter() { + pollables.remove(dead - removed); + removed += 1; + } + dead_pollables = Vec::new(); + } + + if new_pollables.len() > 0 { + for pollable in new_pollables.into_iter() { + pollables.push(pollable); + } + new_pollables = Vec::new(); + } + + if fds_changed { + fds = pollables + .iter() + .map(|x| PollFd::new(x.borrow().fd(), x.borrow().poll_flags())) + .collect(); + } + } +} diff --git a/greetd/src/pollable.rs b/greetd/src/pollable.rs new file mode 100644 index 0000000..426445f --- /dev/null +++ b/greetd/src/pollable.rs @@ -0,0 +1,20 @@ +use std::cell::RefCell; +use std::error::Error; +use std::os::unix::io::RawFd; +use std::rc::Rc; + +use nix::poll::PollFlags; + +use crate::context::Context; + +pub enum PollRunResult { + Uneventful, + Dead, + NewPollable(Rc>>), +} + +pub trait Pollable { + fn fd(&self) -> RawFd; + fn poll_flags(&self) -> PollFlags; + fn run(&mut self, ctx: &mut Context) -> Result>; +} diff --git a/greetd/src/scrambler.rs b/greetd/src/scrambler.rs new file mode 100644 index 0000000..71d9269 --- /dev/null +++ b/greetd/src/scrambler.rs @@ -0,0 +1,27 @@ +use std::default::Default; + +pub trait Scrambler { + fn scramble(&mut self); +} + +impl Scrambler for Vec { + fn scramble(&mut self) { + let cap = self.capacity(); + self.truncate(0); + for _ in 0..cap { + self.push(Default::default()) + } + self.truncate(0); + } +} + +impl Scrambler for String { + fn scramble(&mut self) { + let cap = self.capacity(); + self.truncate(0); + for _ in 0..cap { + self.push(Default::default()) + } + self.truncate(0); + } +} diff --git a/greetd/src/signals.rs b/greetd/src/signals.rs new file mode 100644 index 0000000..c723e48 --- /dev/null +++ b/greetd/src/signals.rs @@ -0,0 +1,51 @@ +use std::error::Error; + +use std::os::unix::io::{AsRawFd, RawFd}; + +use nix::poll::PollFlags; +use nix::sys::signal::{SigSet, Signal}; +use nix::sys::signalfd::{SfdFlags, SignalFd}; + +use crate::context::Context; +use crate::pollable::{PollRunResult, Pollable}; + +pub struct Signals { + listener: SignalFd, +} + +impl Signals { + pub fn new() -> Result> { + let mut mask = SigSet::empty(); + mask.add(Signal::SIGCHLD); + mask.add(Signal::SIGTERM); + mask.thread_block()?; + + let listener = SignalFd::with_flags(&mask, SfdFlags::SFD_NONBLOCK | SfdFlags::SFD_CLOEXEC)?; + + Ok(Signals { listener }) + } +} + +impl Pollable for Signals { + fn fd(&self) -> RawFd { + self.listener.as_raw_fd() + } + + fn poll_flags(&self) -> PollFlags { + PollFlags::POLLIN + } + + fn run(&mut self, ctx: &mut Context) -> Result> { + loop { + match self.listener.read_signal() { + Ok(Some(sig)) => match Signal::from_c_int(sig.ssi_signo as i32)? { + Signal::SIGCHLD => ctx.check_children(), + Signal::SIGTERM => ctx.terminate(), + _ => (), + }, + Ok(None) => break Ok(PollRunResult::Uneventful), + Err(err) => break Err(err.into()), + } + } + } +}