Initial commit

This commit is contained in:
Kenny Levinsen 2019-09-10 00:36:35 +02:00
commit 2571da3772
15 changed files with 865 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
**/*.rs.bk

140
Cargo.lock generated Normal file
View File

@ -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)" = "<none>"
"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"

2
Cargo.toml Normal file
View File

@ -0,0 +1,2 @@
[workspace]
members = ["greetd", "greetctl"]

23
README.md Normal file
View File

@ -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!

11
greetctl/Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name = "greetctl"
version = "0.1.0"
authors = ["Kenny Levinsen <kl@kl.wtf>"]
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"

36
greetctl/src/main.rs Normal file
View File

@ -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<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(username: String, password: String) -> Result<(), Box<dyn std::error::Error>> {
let msg_len = username.len() + password.len() + 8;
let mut buf = Vec::with_capacity(msg_len + 16);
buf.write_u32::<LittleEndian>(0xAFBFCFDF)?; // Proto Magic
buf.write_u32::<LittleEndian>(1)?; // Proto version
buf.write_u32::<LittleEndian>(1)?; // Message type
buf.write_u32::<LittleEndian>(msg_len as u32)?; // Payload length
buf.write_u32::<LittleEndian>(username.len() as u32)?;
buf.extend(username.into_bytes());
buf.write_u32::<LittleEndian>(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();
}

2
greetd/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
**/*.rs.bk

13
greetd/Cargo.toml Normal file
View File

@ -0,0 +1,13 @@
[package]
name = "greetd"
version = "0.1.0"
authors = ["Kenny Levinsen <kl@kl.wtf>"]
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"

165
greetd/src/client.rs Normal file
View File

@ -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<UnixStream>,
buf: Vec<u8>,
state: ClientState,
}
impl Client {
fn read_header(cursor: &mut std::io::Cursor<&[u8]>) -> Result<(u32, u32), Box<dyn Error>> {
let proto_magic = cursor.read_u32::<LittleEndian>()?;
if proto_magic != 0xAFBFCFDF {
return Err(io::Error::new(io::ErrorKind::Other, "invalid message magic").into());
}
let proto_version = cursor.read_u32::<LittleEndian>()?;
if proto_version != 1 {
return Err(io::Error::new(io::ErrorKind::Other, "invalid message version").into());
}
let msg_type = cursor.read_u32::<LittleEndian>()?;
let msg_len = cursor.read_u32::<LittleEndian>()?;
Ok((msg_type, msg_len))
}
fn read_string(cursor: &mut std::io::Cursor<&[u8]>) -> Result<String, Box<dyn Error>> {
let len = cursor.read_u32::<LittleEndian>()?;
let mut data: Vec<u8> = 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<dyn Error>> {
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<Client, Box<dyn Error>> {
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<PollRunResult, Box<dyn Error>> {
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);
}
}
}
}
}
}
}

208
greetd/src/context.rs Normal file
View File

@ -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<Session<'a>>,
greeter: Option<Greeter>,
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<dyn Error>> {
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<dyn Error>> {
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<CString> = 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);
}
}
}

44
greetd/src/listener.rs Normal file
View File

@ -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<Listener, Box<dyn Error>> {
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<PollRunResult, Box<dyn Error>> {
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)?,
)))))
}
}

121
greetd/src/main.rs Normal file
View File

@ -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<Rc<RefCell<Box<dyn Pollable>>>> = vec![
Rc::new(RefCell::new(Box::new(listener))),
Rc::new(RefCell::new(Box::new(signals))),
];
let mut fds: Vec<PollFd> = pollables
.iter()
.map(|x| PollFd::new(x.borrow().fd(), x.borrow().poll_flags()))
.collect();
let mut new_pollables: Vec<Rc<RefCell<Box<dyn Pollable>>>> = Vec::new();
let mut dead_pollables: Vec<usize> = 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();
}
}
}

20
greetd/src/pollable.rs Normal file
View File

@ -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<RefCell<Box<dyn Pollable>>>),
}
pub trait Pollable {
fn fd(&self) -> RawFd;
fn poll_flags(&self) -> PollFlags;
fn run(&mut self, ctx: &mut Context) -> Result<PollRunResult, Box<dyn Error>>;
}

27
greetd/src/scrambler.rs Normal file
View File

@ -0,0 +1,27 @@
use std::default::Default;
pub trait Scrambler {
fn scramble(&mut self);
}
impl<T: Default> Scrambler for Vec<T> {
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);
}
}

51
greetd/src/signals.rs Normal file
View File

@ -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<Signals, Box<dyn Error>> {
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<PollRunResult, Box<dyn Error>> {
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()),
}
}
}
}