Initial commit
This commit is contained in:
commit
2571da3772
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
**/*.rs.bk
|
|
@ -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"
|
|
@ -0,0 +1,2 @@
|
|||
[workspace]
|
||||
members = ["greetd", "greetctl"]
|
|
@ -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!
|
|
@ -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"
|
|
@ -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();
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
**/*.rs.bk
|
|
@ -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"
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)?,
|
||||
)))))
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>>;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue