Skip to content

Commit

Permalink
Use X11 protocol directly via libxcb, instead of xhost command (#163)
Browse files Browse the repository at this point in the history
Use X11 protocol directly via `libxcb`. The `xhost` dependency is no longer needed.
  • Loading branch information
intgr authored Jun 3, 2024
1 parent 5c0ea4f commit 89c24ba
Show file tree
Hide file tree
Showing 11 changed files with 97 additions and 9 deletions.
35 changes: 34 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ log = { version = "0.4.20", features = ["std"] }
shell-words = "1.1.0"
nix = { version = "0.29.0", default-features = false, features = ["user"] }
anstyle = "1.0.4"
xcb = "1.4.0"

[features]
default = []
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ ego (a.k.a Alter Ego)
**Ego** is a tool to run Linux desktop applications under a different local user. Currently
integrates with Wayland, Xorg, PulseAudio and xdg-desktop-portal. You may think of it as `xhost`
for Wayland and PulseAudio. This is done using filesystem ACLs and `xhost` command.
for Wayland and PulseAudio. This is done using filesystem ACLs and X11 host access control.

Disclaimer: **DO NOT RUN UNTRUSTED PROGRAMS VIA EGO.** However, using ego is more secure than
running applications directly under your primary user.
Expand All @@ -32,7 +32,7 @@ Ego aims to come with sane defaults and be easy to set up.
**Requirements:**
* [Rust & cargo](https://www.rust-lang.org/tools/install)
* `libacl.so` library (Debian/Ubuntu: libacl1-dev; Fedora: libacl-devel; Arch: acl)
* `xhost` binary (Debian/Ubuntu: x11-xserver-utils; Fedora: xorg-xhost; Arch: xorg-xhost)
* `libxcb.so` library (Debian/Ubuntu: libxcb1-dev; Fedora: libxcb-devel; Arch: libxcb)

**Recommended:** (Not needed when using `--sudo` mode, but some desktop functionality may not work).
* `machinectl` command (Debian/Ubuntu/Fedora: systemd-container; Arch: systemd)
Expand Down Expand Up @@ -81,6 +81,9 @@ For sudo, add the following to `/etc/sudoers` (replace `<myname>` with your own
Changelog
---------

##### Unreleased
* Use X11 protocol directly via `libxcb`. The `xhost` dependency is no longer needed. (#163)

##### 1.1.7 (2023-06-26)
* Distro packaging: added tmpfiles.d conf to create missing ego user home directory (#134, fixed issue #131)
* Ego now detects and warns when target user's home directory does not exist or has wrong ownership (#139)
Expand Down
8 changes: 8 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub struct Args {
pub command: Vec<String>,
pub log_level: Level,
pub method: Option<Method>,
pub old_xhost: bool,
}

pub fn build_cli() -> Command {
Expand Down Expand Up @@ -47,6 +48,12 @@ pub fn build_cli() -> Command {
.help("Use 'machinectl' but skip xdg-desktop-portal setup"),
)
.group(ArgGroup::new("method").args(["sudo", "machinectl", "machinectl-bare"]))
.arg(
Arg::new("old-xhost")
.long("old-xhost")
.action(ArgAction::SetTrue)
.help("Execute 'xhost' command instead of connecting to X11 directly"),
)
.arg(
Arg::new("command")
.help("Command name and arguments to run (default: user shell)")
Expand Down Expand Up @@ -79,6 +86,7 @@ pub fn parse_args<T: Into<OsString> + Clone>(args: impl IntoIterator<Item = T>)
2 => Level::Debug,
_ => Level::Trace,
},
old_xhost: matches.get_flag("old-xhost"),
method: if matches.get_flag("machinectl") {
Some(Method::Machinectl)
} else if matches.get_flag("machinectl-bare") {
Expand Down
18 changes: 13 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ extern crate simple_error;
use crate::cli::{parse_args, Method};
use crate::errors::{print_error, AnyErr, ErrorWithHint};
use crate::util::{exec_command, have_command, run_command, sd_booted};
use crate::x11::x11_add_acl;
use log::{debug, info, log, warn, Level};
use nix::libc::uid_t;
use nix::unistd::{Uid, User};
Expand All @@ -28,6 +29,7 @@ mod logging;
#[cfg(test)]
mod tests;
mod util;
mod x11;

#[derive(Clone)]
struct EgoContext {
Expand Down Expand Up @@ -60,7 +62,7 @@ fn main_inner() -> Result<(), AnyErr> {
Err(msg) => bail!("Error preparing Wayland: {msg}"),
Ok(ret) => vars.extend(ret),
}
match prepare_x11(&ctx) {
match prepare_x11(&ctx, args.old_xhost) {
Err(msg) => bail!("Error preparing X11: {msg}"),
Ok(ret) => vars.extend(ret),
}
Expand Down Expand Up @@ -233,17 +235,23 @@ fn prepare_wayland(ctx: &EgoContext) -> Result<Vec<String>, AnyErr> {
Ok(vec![format!("WAYLAND_DISPLAY={}", path.to_str().unwrap())])
}

/// Detect `DISPLAY` and run `xhost` to grant permissions.
/// Detect `DISPLAY` and grant permissions via X11 protocol `ChangeHosts` command
/// (or run `xhost` command if `--old-xhost` was used).
/// Return environment vars for `DISPLAY`
fn prepare_x11(ctx: &EgoContext) -> Result<Vec<String>, AnyErr> {
fn prepare_x11(ctx: &EgoContext, old_xhost: bool) -> Result<Vec<String>, AnyErr> {
let display = getenv_optional("DISPLAY")?;
if display.is_none() {
debug!("X11: DISPLAY not set, skipping");
return Ok(vec![]);
}

let grant = format!("+si:localuser:{}", ctx.target_user);
run_command("xhost", &[grant])?;
if old_xhost {
warn!("--old-xhost is deprecated. If there are issues with the new method, please report a bug.");
let grant = format!("+si:localuser:{}", ctx.target_user);
run_command("xhost", &[grant])?;
} else {
x11_add_acl("localuser", &ctx.target_user)?;
}
// TODO should also test /tmp/.X11-unix/X0 permissions?

Ok(vec![format!("DISPLAY={}", display.unwrap())])
Expand Down
1 change: 1 addition & 0 deletions src/snapshots/ego.help
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Options:
--sudo Use 'sudo' to change user
--machinectl Use 'machinectl' to change user (default, if available)
--machinectl-bare Use 'machinectl' but skip xdg-desktop-portal setup
--old-xhost Execute 'xhost' command instead of connecting to X11 directly
-v, --verbose... Verbose output. Use multiple times for more output.
-h, --help Print help
-V, --version Print version
12 changes: 12 additions & 0 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use snapbox::{file, Data};

use crate::cli::{build_cli, parse_args, Method};
use crate::util::have_command;
use crate::x11::x11_add_acl;
use crate::{check_user_homedir, get_wayland_socket, EgoContext};

/// `vec![]` constructor that converts arguments to String
Expand Down Expand Up @@ -107,6 +108,17 @@ fn wayland_socket() {
);
}

#[test]
fn test_x11_error() {
env::remove_var("DISPLAY");

let err = x11_add_acl("test", "test").unwrap_err();
assert_eq!(
err.to_string(),
"Connection closed, error during parsing display string"
);
}

#[test]
fn test_cli() {
build_cli().debug_assert();
Expand Down
20 changes: 20 additions & 0 deletions src/x11.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use log::debug;
use xcb::x::{ChangeHosts, Family, HostMode};
use xcb::Connection;

use crate::errors::AnyErr;

pub fn x11_add_acl(type_tag: &str, value: &str) -> Result<(), AnyErr> {
let (conn, _screen_num) = Connection::connect(None)?;

debug!("X11: Adding XHost entry SI:{type_tag}:{value}");

let result = conn.send_and_check_request(&ChangeHosts {
mode: HostMode::Insert,
family: Family::ServerInterpreted,
address: format!("{type_tag}\x00{value}").as_bytes(),
});
map_err_with!(result, "Error adding XHost entry")?;

Ok(())
}
2 changes: 1 addition & 1 deletion varia/ego-completion.bash
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ _ego() {

case "${cmd}" in
ego)
opts="-u -v -h -V --user --sudo --machinectl --machinectl-bare --verbose --help --version [command]..."
opts="-u -v -h -V --user --sudo --machinectl --machinectl-bare --old-xhost --verbose --help --version [command]..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
Expand Down
1 change: 1 addition & 0 deletions varia/ego-completion.fish
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ complete -c ego -s u -l user -d 'Specify a username (default: ego)' -r -f -a "(_
complete -c ego -l sudo -d 'Use \'sudo\' to change user'
complete -c ego -l machinectl -d 'Use \'machinectl\' to change user (default, if available)'
complete -c ego -l machinectl-bare -d 'Use \'machinectl\' but skip xdg-desktop-portal setup'
complete -c ego -l old-xhost -d 'Execute \'xhost\' command instead of connecting to X11 directly'
complete -c ego -s v -l verbose -d 'Verbose output. Use multiple times for more output.'
complete -c ego -s h -l help -d 'Print help'
complete -c ego -s V -l version -d 'Print version'
1 change: 1 addition & 0 deletions varia/ego-completion.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ _ego() {
'--sudo[Use '\''sudo'\'' to change user]' \
'--machinectl[Use '\''machinectl'\'' to change user (default, if available)]' \
'--machinectl-bare[Use '\''machinectl'\'' but skip xdg-desktop-portal setup]' \
'--old-xhost[Execute '\''xhost'\'' command instead of connecting to X11 directly]' \
'*-v[Verbose output. Use multiple times for more output.]' \
'*--verbose[Verbose output. Use multiple times for more output.]' \
'-h[Print help]' \
Expand Down

0 comments on commit 89c24ba

Please sign in to comment.