Skip to content

Commit 89c24ba

Browse files
authored
Use X11 protocol directly via libxcb, instead of xhost command (#163)
Use X11 protocol directly via `libxcb`. The `xhost` dependency is no longer needed.
1 parent 5c0ea4f commit 89c24ba

File tree

11 files changed

+97
-9
lines changed

11 files changed

+97
-9
lines changed

Cargo.lock

Lines changed: 34 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ log = { version = "0.4.20", features = ["std"] }
2323
shell-words = "1.1.0"
2424
nix = { version = "0.29.0", default-features = false, features = ["user"] }
2525
anstyle = "1.0.4"
26+
xcb = "1.4.0"
2627

2728
[features]
2829
default = []

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ ego (a.k.a Alter Ego)
1010
1111
**Ego** is a tool to run Linux desktop applications under a different local user. Currently
1212
integrates with Wayland, Xorg, PulseAudio and xdg-desktop-portal. You may think of it as `xhost`
13-
for Wayland and PulseAudio. This is done using filesystem ACLs and `xhost` command.
13+
for Wayland and PulseAudio. This is done using filesystem ACLs and X11 host access control.
1414

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

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

84+
##### Unreleased
85+
* Use X11 protocol directly via `libxcb`. The `xhost` dependency is no longer needed. (#163)
86+
8487
##### 1.1.7 (2023-06-26)
8588
* Distro packaging: added tmpfiles.d conf to create missing ego user home directory (#134, fixed issue #131)
8689
* Ego now detects and warns when target user's home directory does not exist or has wrong ownership (#139)

src/cli.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub struct Args {
1515
pub command: Vec<String>,
1616
pub log_level: Level,
1717
pub method: Option<Method>,
18+
pub old_xhost: bool,
1819
}
1920

2021
pub fn build_cli() -> Command {
@@ -47,6 +48,12 @@ pub fn build_cli() -> Command {
4748
.help("Use 'machinectl' but skip xdg-desktop-portal setup"),
4849
)
4950
.group(ArgGroup::new("method").args(["sudo", "machinectl", "machinectl-bare"]))
51+
.arg(
52+
Arg::new("old-xhost")
53+
.long("old-xhost")
54+
.action(ArgAction::SetTrue)
55+
.help("Execute 'xhost' command instead of connecting to X11 directly"),
56+
)
5057
.arg(
5158
Arg::new("command")
5259
.help("Command name and arguments to run (default: user shell)")
@@ -79,6 +86,7 @@ pub fn parse_args<T: Into<OsString> + Clone>(args: impl IntoIterator<Item = T>)
7986
2 => Level::Debug,
8087
_ => Level::Trace,
8188
},
89+
old_xhost: matches.get_flag("old-xhost"),
8290
method: if matches.get_flag("machinectl") {
8391
Some(Method::Machinectl)
8492
} else if matches.get_flag("machinectl-bare") {

src/main.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ extern crate simple_error;
77
use crate::cli::{parse_args, Method};
88
use crate::errors::{print_error, AnyErr, ErrorWithHint};
99
use crate::util::{exec_command, have_command, run_command, sd_booted};
10+
use crate::x11::x11_add_acl;
1011
use log::{debug, info, log, warn, Level};
1112
use nix::libc::uid_t;
1213
use nix::unistd::{Uid, User};
@@ -28,6 +29,7 @@ mod logging;
2829
#[cfg(test)]
2930
mod tests;
3031
mod util;
32+
mod x11;
3133

3234
#[derive(Clone)]
3335
struct EgoContext {
@@ -60,7 +62,7 @@ fn main_inner() -> Result<(), AnyErr> {
6062
Err(msg) => bail!("Error preparing Wayland: {msg}"),
6163
Ok(ret) => vars.extend(ret),
6264
}
63-
match prepare_x11(&ctx) {
65+
match prepare_x11(&ctx, args.old_xhost) {
6466
Err(msg) => bail!("Error preparing X11: {msg}"),
6567
Ok(ret) => vars.extend(ret),
6668
}
@@ -233,17 +235,23 @@ fn prepare_wayland(ctx: &EgoContext) -> Result<Vec<String>, AnyErr> {
233235
Ok(vec![format!("WAYLAND_DISPLAY={}", path.to_str().unwrap())])
234236
}
235237

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

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

249257
Ok(vec![format!("DISPLAY={}", display.unwrap())])

src/snapshots/ego.help

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Options:
1010
--sudo Use 'sudo' to change user
1111
--machinectl Use 'machinectl' to change user (default, if available)
1212
--machinectl-bare Use 'machinectl' but skip xdg-desktop-portal setup
13+
--old-xhost Execute 'xhost' command instead of connecting to X11 directly
1314
-v, --verbose... Verbose output. Use multiple times for more output.
1415
-h, --help Print help
1516
-V, --version Print version

src/tests.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use snapbox::{file, Data};
1111

1212
use crate::cli::{build_cli, parse_args, Method};
1313
use crate::util::have_command;
14+
use crate::x11::x11_add_acl;
1415
use crate::{check_user_homedir, get_wayland_socket, EgoContext};
1516

1617
/// `vec![]` constructor that converts arguments to String
@@ -107,6 +108,17 @@ fn wayland_socket() {
107108
);
108109
}
109110

111+
#[test]
112+
fn test_x11_error() {
113+
env::remove_var("DISPLAY");
114+
115+
let err = x11_add_acl("test", "test").unwrap_err();
116+
assert_eq!(
117+
err.to_string(),
118+
"Connection closed, error during parsing display string"
119+
);
120+
}
121+
110122
#[test]
111123
fn test_cli() {
112124
build_cli().debug_assert();

src/x11.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use log::debug;
2+
use xcb::x::{ChangeHosts, Family, HostMode};
3+
use xcb::Connection;
4+
5+
use crate::errors::AnyErr;
6+
7+
pub fn x11_add_acl(type_tag: &str, value: &str) -> Result<(), AnyErr> {
8+
let (conn, _screen_num) = Connection::connect(None)?;
9+
10+
debug!("X11: Adding XHost entry SI:{type_tag}:{value}");
11+
12+
let result = conn.send_and_check_request(&ChangeHosts {
13+
mode: HostMode::Insert,
14+
family: Family::ServerInterpreted,
15+
address: format!("{type_tag}\x00{value}").as_bytes(),
16+
});
17+
map_err_with!(result, "Error adding XHost entry")?;
18+
19+
Ok(())
20+
}

varia/ego-completion.bash

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ _ego() {
1919

2020
case "${cmd}" in
2121
ego)
22-
opts="-u -v -h -V --user --sudo --machinectl --machinectl-bare --verbose --help --version [command]..."
22+
opts="-u -v -h -V --user --sudo --machinectl --machinectl-bare --old-xhost --verbose --help --version [command]..."
2323
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
2424
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
2525
return 0

varia/ego-completion.fish

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ complete -c ego -s u -l user -d 'Specify a username (default: ego)' -r -f -a "(_
22
complete -c ego -l sudo -d 'Use \'sudo\' to change user'
33
complete -c ego -l machinectl -d 'Use \'machinectl\' to change user (default, if available)'
44
complete -c ego -l machinectl-bare -d 'Use \'machinectl\' but skip xdg-desktop-portal setup'
5+
complete -c ego -l old-xhost -d 'Execute \'xhost\' command instead of connecting to X11 directly'
56
complete -c ego -s v -l verbose -d 'Verbose output. Use multiple times for more output.'
67
complete -c ego -s h -l help -d 'Print help'
78
complete -c ego -s V -l version -d 'Print version'

0 commit comments

Comments
 (0)