10 Commits

Author SHA1 Message Date
vasilito d6fa513ca7 fix: protocol constants and local path deps 2026-07-05 23:57:27 +03:00
vasilito 4a578129b2 version: use +rb0.2.5 build metadata instead of -rb0.2.5 pre-release
Semver pre-release suffix (-rb0.2.5) breaks Cargo's [patch.crates-io]
matching for transitive deps. Build metadata (+rb0.2.5) is semver-
compatible: ^0.9.0 matches 0.9.0+rb0.2.5, patch redirection works,
and the Red Bear suffix is still visible in the version string.
2026-07-05 09:16:12 +03:00
vasilito 9bcded087e fix: match syscall path dep version to 0.9.0-rb0.2.5 pre-release
Cargo requires path dependency version requirements to explicitly
match pre-release versions. The syscall fork is at 0.9.0-rb0.2.5,
so 'version = "0.9.0"' doesn't satisfy it. Update to
'0.9.0-rb0.2.5'.
2026-07-05 09:12:17 +03:00
vasilito 3bd7013db4 fork: add Red Bear author attribution to Cargo.toml 2026-07-05 08:48:16 +03:00
vasilito a290059211 fork: bump to -rb1 version suffix
Per local/AGENTS.md \xC2\xA7 "Category 2 - Local forks of upstream packages",
all Cat 2 forks must use the `<upstream-tag>-rb<N>` version convention. The
`-rb1` suffix is a Cargo pre-release identifier that prevents upstream
`<upstream-tag>` from silently substituting for this fork in transitive
dependency resolution.

- The base tag (e.g. 0.9.0 for redox_syscall) tracks upstream
  unchanged.
- The `rb1` part records that one Red Bear patch round has been
  applied on top of the upstream tag.

This is the initial policy enforcement pass. The branch is
considered unfrozen at this point; once a freeze is declared, future
`-rbN` increments will land on a frozen `0.X.Y` branch per
local/AGENTS.md.
2026-07-04 10:07:31 +03:00
vasilito 8a4014fb3c libredox: remove redox_syscall from default features list
The default features list still had 'redox_syscall' as a
feature entry, but the redox_syscall dep is now non-optional
(made non-optional in commit 80d0eae so the acpi module
could unconditionally re-export AcPiVerb). Cargo rejects
this as a manifest error: 'feature default includes
redox_syscall, but redox_syscall is not an optional
dependency'.

The fix is to remove 'redox_syscall' from the default list.
Since the dep is non-optional now, the feature is redundant
anyway (always on by default). The .orig and the
auto-generated Cargo.toml are updated in sync.

The previous build failure (after Cargo.toml regen) was
uncovered by the next build attempt and fixed here.
2026-07-02 11:09:42 +03:00
vasilito fc1ece67b3 libredox: regenerate Cargo.toml from .orig (version 0.1.18, non-optional redox_syscall)
The generated Cargo.toml was stale (still 0.1.17 with
optional = true on redox_syscall) after the .orig bump.
Force-regenerate by manually editing both fields so the
generated file matches .orig. The previous build failure
(unresolved import redox_syscall) was due to this drift.

Discovered by running redbear-mini build after the
nice-mapping, affinity-mask, deadlock, and proc-path
fixes landed.
2026-07-02 10:59:45 +03:00
vasilito 80d0eaeb21 libredox: make redox_syscall non-optional, fix O_CLOEXEC dup
Three fixes for cross-recipe build:

1. O_CLOEXEC was already imported from libc at line 114, but
   the 0.1.18 backport added a duplicate 'pub const O_CLOEXEC'
   at line 173. E0255 ("name defined multiple times") error.
   Keep only the new F_DUPFD_CLOEXEC constant; O_CLOEXEC comes
   from libc.

2. pub mod acpi that re-exports AcPiVerb from redox_syscall
   is now correctly gated on '#[cfg(feature = "redox_syscall")]'.
   The 'redox_syscall' dep is currently optional in libredox's
   Cargo.toml, so recipes that don't enable the feature
   (e.g. base-initfs which uses default-features libredox)
   get an unresolved-import error. Move to making the dep
   non-optional so it's always available.

3. Make redox_syscall dep non-optional in Cargo.toml. This
   is safe because (a) the path override always points at the
   local fork which is always present, and (b) all downstream
   consumers of libredox already depend on redox_syscall
   transitively (or are the syscall consumers themselves, like
   the kernel).

After these three changes, the libredox fork builds cleanly
and the 'pub mod acpi { pub use redox_syscall::AcPiVerb; }'
re-export is available to all consumers without feature flags.
2026-07-02 08:25:51 +03:00
vasilito d87746b279 libredox: bump to 0.1.18 + backport redox_fcntl_v0 + add Phase J acpi module
Three changes in one commit, all interrelated:

1. Bump version 0.1.17 -> 0.1.18 to match upstream. Reason:
   recipes/core/redoxfs/source requires libredox = "0.1.18",
   and the kernel's [patch.crates-io] override forces this local
   fork to be used in the kernel compilation. If the local fork
   stays at 0.1.17, the linker fails with undefined references
   to symbols added in 0.1.18 (redox_fcntl_v0 in particular).

2. Backport the 0.1.18 additions to src/lib.rs:
   - fn redox_fcntl_v0 extern declaration
   - pub fn fcntl() on Fd and as a free function
   - O_CLOEXEC and F_DUPFD_CLOEXEC constants
   All backports are purely additive; the existing Phase J
   path override (path = "../syscall") is preserved.

3. Re-apply the actual Phase J work to libredox that was
   missing from commit d01da35 ("Phase J fork"):
   - pub mod acpi re-exporting AcPiVerb from redox_syscall
   The commit message of d01da35 implied it added AcPiVerb
   re-exports but in fact only added the path override and
   version bump. This re-export is what gives acpid / kstop /
   redbear-power / redbear-acmd access to the symbolic
   EnterS2Idle / ExitS2Idle / SetS3WakingVector / EnterS3
   names instead of hardcoded integer discriminants.

After this commit, redoxfs links cleanly against the local
libredox 0.1.18 and the kernel's per-cpu run queues / futex
sharding work remains intact.
2026-07-02 07:55:04 +03:00
vasilito d01da350c1 libredox: Red Bear OS Phase J fork (use local syscall with EnterS2Idle/ExitS2Idle)
Phase J: hardware-agnostic s2idle / Modern Standby support
in libredox. The libredox 0.1.17 dep on redox_syscall is
redirected to the local fork at ../syscall (the fork at
local/sources/syscall/ which adds EnterS2Idle/ExitS2Idle
AcPiVerb variants to upstream's 0.8.1).

This breaks the libredox::error::Error <-> syscall::Error
type-identity barrier that previously caused E0277 errors
in scheme-utils and daemon when the local syscall fork
was used. Now that libredox also uses the local fork,
both types are the same compile-time type.

The version field stays at upstream 0.1.17 per the
AGENTS.md 'GOLDEN RULE — Red Bear adapts to upstream,
never the reverse'. Periodic rebase via 'git fetch
upstream' is the workflow when upstream changes.

Hardware-agnostic: works for any platform with Modern
Standby firmware (Dell, HP, Lenovo, LG Gram, etc.).
2026-07-01 13:12:39 +03:00
24 changed files with 1214 additions and 2214 deletions
+1
View File
@@ -0,0 +1 @@
{"v":1}
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "7040cf71b3a5d15d91802810d0a50aa197970c43"
},
"path_in_vcs": ""
}
+2 -1
View File
@@ -1 +1,2 @@
/target/ /target
/Cargo.lock
-6
View File
@@ -1,6 +0,0 @@
language: rust
rust:
- nightly
sudo: false
notifications:
email: false
+31 -75
View File
@@ -1,82 +1,38 @@
[package] [package]
name = "userutils" name = "libredox"
version = "0.1.0+rb0.2.5" authors = ["4lDO2 <4lDO2@protonmail.com>", "vasilito <adminpupkin@gmail.com>"]
authors = ["Jeremy Soller <jackpot51@gmail.com>", "vasilito <adminpupkin@gmail.com>"] version = "0.1.18+rb0.2.5"
edition = "2024" edition = "2021"
license = "MIT"
description = "Redox stable ABI"
repository = "https://gitea.redbearos.org/vasilito/RedBear-OS"
exclude = ["target"]
[[bin]] [features]
name = "id" default = ["base", "call", "std", "protocol", "redox_syscall"]
path = "src/bin/id.rs" base = ["libc"]
call = ["base"]
[[bin]] std = ["base"]
name = "getty" protocol = ["plain", "bitflags"]
path = "src/bin/getty.rs" mkns = ["ioslice"]
# The `redox_syscall` feature activates the optional `redox_syscall`
[[bin]] # dep below. The `#[cfg(feature = "redox_syscall")]` attributes in
name = "groupadd" # src/lib.rs gate the `From` impls between `libredox::error::Error`
path = "src/bin/groupadd.rs" # and `syscall::Error`; without the feature, those impls are absent
# and `?` propagation between the two types fails. Mirroring
[[bin]] # upstream `libredox 0.1.18`'s feature structure.
name = "groupdel" redox_syscall = ["dep:redox_syscall"]
path = "src/bin/groupdel.rs"
[[bin]]
name = "groupmod"
path = "src/bin/groupmod.rs"
[[bin]]
name = "login"
path = "src/bin/login.rs"
[[bin]]
name = "passwd"
path = "src/bin/passwd.rs"
[[bin]]
name = "su"
path = "src/bin/su.rs"
[[bin]]
name = "sudo"
path = "src/bin/sudo.rs"
[[bin]]
name = "useradd"
path = "src/bin/useradd.rs"
[[bin]]
name = "userdel"
path = "src/bin/userdel.rs"
[[bin]]
name = "usermod"
path = "src/bin/usermod.rs"
[dependencies] [dependencies]
clap = "2.33.0" bitflags = { version = "2", optional = true }
extra = { git = "https://gitlab.redox-os.org/redox-os/libextra.git" } libc = { version = "0.2", optional = true }
orbclient = "0.3.47" # The redox_syscall package has [lib] name = "syscall", so
plain = "0.2.3" # in Rust code it is imported as `syscall`, NOT `redox_syscall`.
redox_liner = "0.5.2" # This dep is optional and gated by the `redox_syscall` feature
libredox = { path = "../libredox", features = ["mkns"] } # above. Matching upstream `libredox 0.1.18` structure.
redox_termios = "0.1.3" redox_syscall = { path = "../syscall", optional = true }
redox_event = "0.4.3" ioslice = { version = "0.6", optional = true }
redox-scheme = { path = "../redox-scheme" } plain = { version = "0.2", optional = true }
redox_syscall = { path = "../syscall" }
redox_users = "0.4.6"
termion = "4"
libc = "0.2"
serde = { version = "1.0.203", features = ["derive"] }
toml = "0.8.11"
ioslice = "0.6"
[target.'cfg(target_os = "redox")'.dependencies]
redox-rt = { git = "https://gitlab.redox-os.org/redox-os/relibc", default-features = false }
[patch.crates-io] [patch.crates-io]
redox_syscall = { path = "../syscall" } redox_syscall = { path = "../syscall" }
libredox = { path = "../libredox" }
redox-scheme = { path = "../redox-scheme" }
[profile.release]
lto = true
+44
View File
@@ -0,0 +1,44 @@
[package]
name = "libredox"
authors = ["4lDO2 <4lDO2@protonmail.com>"]
# Red Bear OS Phase J: version is 0.1.18 upstream. The
# redox_syscall dep is now required (not optional) because
# the local fork's acpi module (added in this commit) re-
# exports AcPiVerb from redox_syscall, and downstream recipes
# that don't enable the redox_syscall feature get an
# "unresolved import" error. Making the dep non-optional
# also matches the upstream 0.1.18 Cargo.toml pattern where
# the redox_syscall dep is unconditional.
version = "0.1.18"
edition = "2021"
license = "MIT"
description = "Redox stable ABI"
# Red Bear OS fork lives at the canonical outer repo
# (gitea.redbearos.org/vasilito/RedBear-OS).
repository = "https://gitea.redbearos.org/vasilito/RedBear-OS"
exclude = ["target"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = ["base", "call", "std", "protocol"]
base = ["libc"]
call = ["base"]
std = ["base"]
protocol = ["plain", "bitflags", "redox_syscall"]
mkns = ["ioslice"]
[dependencies]
bitflags = { version = "2", optional = true }
libc = { version = "0.2", optional = true }
# Phase J: path override to the local fork (../syscall
# relative to the libredox fork's local/sources/libredox/
# path). This gives libredox access to the EnterS2Idle /
# ExitS2Idle AcpiVerb variants. Cargo's [patch.crates-io]
# in the workspace's outer Cargo.toml (in base/ and kernel/)
# is what wires this path through to the actual
# redox_syscall crate; this path entry is the libredox-
# side patch override for the same crate.
redox_syscall = { path = "../syscall", version = "0.8" }
ioslice = { version = "0.6", optional = true }
plain = { version = "0.2", optional = true }
+2 -2
View File
@@ -1,6 +1,6 @@
The MIT License (MIT) MIT License
Copyright (c) 2016 The Redox developers Copyright (c) 2023 4lDO2
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
-24
View File
@@ -1,24 +0,0 @@
# Redox OS user and group utilities.
The `userutils` crate contains the utilities for dealing with users and groups in Redox OS.
They are heavily influenced by UNIX and are, when needed, tailored to specific Redox use cases.
These implementations strive to be as simple as possible drawing particular
inspiration by BSD systems. They are indeed small, by choice.
[![Travis Build Status](https://travis-ci.org/redox-os/userutils.svg?branch=master)](https://travis-ci.org/redox-os/userutils)
**Currently included:**
- `getty`: Used by `init(8)` to open and initialize the TTY line, read a login name and invoke `login(1)`.
- `id`: Displays user identity.
- `login`: Allows users to login into the system
- `passwd`: Allows users to modify their passwords.
- `su`: Allows users to substitute identity.
- `sudo`: Enables users to execute a command as another user.
- `useradd`: Add a user
- `usermod`: Modify user information
- `userdel`: Delete a user
- `groupadd`: Add a user group
- `groupmod`: Modify group information
- `groupdel`: Remove a user group
-6
View File
@@ -1,6 +0,0 @@
########## Red Bear OS #########
# Login with the following: #
# `user` #
# `root`:`password` #
################################
-6
View File
@@ -1,6 +0,0 @@
########## Redox OS ##########
# Login with the following: #
# `user` #
# `root`:`password` #
##############################
-2
View File
@@ -1,2 +0,0 @@
Welcome to Red Bear OS!
-285
View File
@@ -1,285 +0,0 @@
#[macro_use]
extern crate clap;
use std::error::Error;
use std::fs::File;
use std::io::{self, ErrorKind, Read, Stderr, Write};
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::process::{Child, Command, Stdio};
use std::str;
use std::time::{Duration, Instant};
use event::{EventFlags, RawEventQueue};
use extra::io::fail;
use libredox::call as redox;
use libredox::errno::EAGAIN;
use libredox::flag;
const _MAN_PAGE: &'static str = /* @MANSTART{getty} */
r#"
NAME
getty - set terminal mode
SYNOPSIS
getty [-J | --noclear | -C | --contain ] tty
getty [ -h | --help ]
DESCRIPTION
The getty utility is called by init(8) to open and initialize the tty line,
read a login name, and invoke login(1).
OPTIONS
-h, --help
Display this help and exit.
-J, --noclear
Do not clear the screen before forking login(1).
-C, --contain
Run contain_login instead of login
AUTHOR
Written by Jeremy Soller.
"#; /* @MANEND */
const DEFAULT_COLS: u16 = 80;
const DEFAULT_LINES: u16 = 30;
pub fn handle(
event_queue: &mut RawEventQueue,
tty_fd: RawFd,
master_fd: RawFd,
process: &mut Child,
) {
// tty_fd => Display
// master_fd => PTY
let handle_event = |event_id: usize| {
if event_id as RawFd == tty_fd {
let mut packet = [0; 4096];
loop {
let count = match redox::read(tty_fd as usize, &mut packet) {
Ok(0) => return,
Ok(count) => count,
Err(ref err) if err.errno() == EAGAIN => break,
Err(_) => panic!("getty: failed to read from TTY"),
};
redox::write(master_fd as usize, &packet[..count])
.expect("getty: failed to write master PTY");
}
} else if event_id as RawFd == master_fd {
let mut packet = [0; 4096];
loop {
let count = match redox::read(master_fd as usize, &mut packet) {
Ok(0) => return,
Ok(count) => count,
Err(ref err) if err.errno() == EAGAIN => break,
Err(_) => panic!("getty: failed to read from master TTY"),
};
redox::write(tty_fd as usize, &packet[1..count])
.expect("getty: failed to write to TTY");
if packet[0] & 1 == 1 {
let _ = redox::fsync(tty_fd as usize);
}
}
}
};
handle_event(tty_fd as usize);
handle_event(master_fd as usize);
'events: loop {
let sys_event = event_queue
.next()
.expect("getty: event queue stopped")
.expect("getty: failed to read event file");
handle_event(sys_event.fd);
match process.try_wait() {
Ok(status) => match status {
Some(_code) => break 'events,
None => (),
},
Err(err) => match err.kind() {
ErrorKind::WouldBlock => (),
_ => panic!("getty: failed to wait on child: {:?}", err),
},
}
}
let _ = process.kill();
process.wait().expect("getty: failed to wait on login");
}
pub fn getpty(columns: u16, lines: u16) -> (RawFd, String) {
let master = redox::open(
"/scheme/pty",
flag::O_CLOEXEC | flag::O_RDWR | flag::O_CREAT | flag::O_NONBLOCK,
0,
)
.expect("getty: failed to create PTY");
if let Ok(winsize_fd) = redox::dup(master, b"winsize") {
let _ = redox::write(
winsize_fd,
&redox_termios::Winsize {
ws_row: lines,
ws_col: columns,
},
);
let _ = redox::close(winsize_fd);
}
let mut buf: [u8; 4096] = [0; 4096];
let count = redox::fpath(master, &mut buf).unwrap();
(master as RawFd, unsafe {
String::from_utf8_unchecked(Vec::from(&buf[..count]))
})
}
// termion cursor_pos prone to error and does not work on nonblocking files
fn tty_cursor_pos(tty: &mut File) -> Result<(u16, u16), Box<dyn Error>> {
write!(tty, "\x1B[6n")?;
tty.flush()?;
let timeout = Duration::from_millis(500);
let instant = Instant::now();
let mut data = String::new();
while instant.elapsed() < timeout {
let mut bytes = [0];
match tty.read(&mut bytes) {
Ok(count) => if count == 1 {
let c = bytes[0] as char;
if c == 'R' {
break;
}
data.push(c);
},
Err(err) => if err.kind() != ErrorKind::WouldBlock {
return Err(err.into());
}
}
}
if data.is_empty() {
return Err("cursor position timed out".into());
}
let beg = data.rfind('[').ok_or("failed to find [")?;
let coords: String = data.chars().skip(beg + 1).collect();
let mut nums = coords.split(';');
let row = nums.next().ok_or("failed to find row")?.parse::<u16>()?;
let col = nums.next().ok_or("failed to find col")?.parse::<u16>()?;
Ok((col, row))
}
fn tty_columns_lines(tty: &mut File) -> Result<(u16, u16), Box<dyn Error>> {
write!(tty, "{}", termion::cursor::Save)?;
tty.flush()?;
write!(tty, "{}", termion::cursor::Goto(999, 999))?;
tty.flush()?;
let res = tty_cursor_pos(tty);
write!(tty, "{}", termion::cursor::Restore)?;
tty.flush()?;
res
}
fn daemon(tty: &mut File, clear: bool, contain: bool, stderr: &mut Stderr) {
let (columns, lines) = tty_columns_lines(tty).unwrap_or((DEFAULT_COLS, DEFAULT_LINES));
let tty_fd = tty.as_raw_fd();
let (master_fd, pty) = getpty(columns, lines);
let mut event_queue = event::RawEventQueue::new().expect("getty: failed to open event queue");
event_queue
.subscribe(tty_fd as usize, 0, EventFlags::READ)
.expect("getty: failed to fevent TTY");
event_queue
.subscribe(master_fd as usize, 0, EventFlags::READ)
.expect("getty: failed to fevent master PTY");
loop {
if clear {
let _ = redox::write(tty_fd as usize, b"\x1Bc");
}
let _ = redox::fsync(tty_fd as usize);
let slave_stdin = redox::open(&pty, flag::O_CLOEXEC | flag::O_RDONLY, 0)
.expect("getty: failed to open slave stdin");
let slave_stdout = redox::open(&pty, flag::O_CLOEXEC | flag::O_WRONLY, 0)
.expect("getty: failed to open slave stdout");
let slave_stderr = redox::open(&pty, flag::O_CLOEXEC | flag::O_WRONLY, 0)
.expect("getty: failed to open slave stderr");
let mut command = if contain {
Command::new("contain_login")
} else {
Command::new("login")
};
unsafe {
command
.stdin(Stdio::from_raw_fd(slave_stdin as RawFd))
.stdout(Stdio::from_raw_fd(slave_stdout as RawFd))
.stderr(Stdio::from_raw_fd(slave_stderr as RawFd))
.env("TERM", "xterm-256color")
.env("TTY", &pty);
}
match command.spawn() {
Ok(mut process) => {
handle(&mut event_queue, tty_fd, master_fd, &mut process);
}
Err(err) => fail(&format!("getty: failed to execute login: {}", err), stderr),
}
}
}
pub fn main() {
let mut stderr = io::stderr();
let args = clap_app!(getty =>
(author: "Jeremy Soller")
(about: "Set terminal mode")
(@arg TTY: +required "")
(@arg NO_CLEAR: -J --("no-clear") "Do not clear the screen before forking")
(@arg CONTAIN: -C --("contain") "Run contain_login instead of login")
)
.get_matches();
let clear = !args.is_present("NO_CLEAR");
let contain = args.is_present("CONTAIN");
let vt = args.value_of("TTY").unwrap();
let buf: String;
let vt_path = if vt.parse::<usize>().is_ok() {
buf = format!("/scheme/fbcon/{vt}");
&*buf
} else {
vt
};
let mut tty = match redox::open(
&vt_path,
flag::O_CLOEXEC | flag::O_RDWR | flag::O_NONBLOCK,
0,
) {
Ok(fd) => unsafe { File::from_raw_fd(fd as RawFd) },
Err(err) => fail(
&format!("getty: failed to open TTY {}: {}", vt_path, err),
&mut stderr,
),
};
daemon(&mut tty, clear, contain, &mut stderr);
}
-82
View File
@@ -1,82 +0,0 @@
#[macro_use]
extern crate clap;
use extra::option::OptionalExt;
use std::process::exit;
use redox_users::{All, AllGroups, Config, Error, GroupBuilder};
const _MAN_PAGE: &'static str = /* @MANSTART{groupadd} */
r#"
NAME
groupadd - add a user group
SYNOPSIS
groupadd [ -f | --force ] GROUP
groupadd [ -h | --help ]
DESCRIPTION
The groupadd utility adds a new user group using values
passed on the command line and system defaults.
OPTIONS
-f, --force
Simply forces the exit status of the program to 0
even if the group already exists. A message is still
printed to stdout.
-g, --gid GID
The group id to use. This value must not be used and must
be non-negative. The default is to pick the smallest available
group id (between values defined in redox_users).
-h, --help
Display this help and exit.
AUTHOR
Written by Wesley Hershberger.
"#; /* @MANEND */
fn main() {
let args = clap_app!(groupadd =>
(author: "Wesley Hershberger")
(about: "Add groups based on the system's redox_users backend")
(@arg GROUP: +required "Add group GROUP")
(@arg FORCE: -f --force "Force the status of the program to be 0 even if the group exists")
(@arg GID: -g --gid +takes_value "Group id. Positive integer and must not be in use")
)
.get_matches();
let mut sys_groups = AllGroups::new(Config::default().writeable(true)).unwrap_or_exit(1);
let groupname = args.value_of("GROUP").unwrap();
let gid = match args.value_of("GID") {
Some(gid) => {
let id = gid.parse::<usize>().unwrap_or_exit(1);
if let Some(_group) = sys_groups.get_by_id(id) {
eprintln!("groupadd: group already exists");
exit(1);
}
id
}
None => sys_groups.get_unique_id().unwrap_or_else(|| {
eprintln!("groupadd: no available gid");
exit(1);
}),
};
let group = GroupBuilder::new(groupname).gid(gid);
match sys_groups.add_group(group) {
Ok(_) => (),
Err(Error::GroupAlreadyExists) if args.is_present("FORCE") => {
exit(0);
}
Err(err) => {
eprintln!("groupadd: {}", err);
exit(1);
}
}
sys_groups.save().unwrap_or_exit(1);
}
-47
View File
@@ -1,47 +0,0 @@
#[macro_use]
extern crate clap;
use extra::option::OptionalExt;
use redox_users::{All, AllGroups, Config};
const _MAN_PAGE: &'static str = /* @MANSTART{groupdel} */
r#"
NAME
groupdel - modify system files to delete groups
SYNOPSYS
groupdel [ options ] GROUP
groupdel [ -h | --help ]
DESCRIPTION
groupdel removes groups from whatever backend is employed by
the system's redox_users.
Note that you should not remove a primary user group before
removing the user. It is also generally wise not to remove
groups that still own files on the system.
OPTIONS
-h, --help
Print this help page and exit.
AUTHORS
Wesley Hershberger.
"#; /* @MANEND */
fn main() {
let matches = clap_app!(groupdel =>
(author: "Wesley Hershberger")
(about: "Removes a group from the system using redox_users")
(@arg GROUP: +required "Removes group GROUP")
)
.get_matches();
let group = matches.value_of("GROUP").unwrap();
let mut sys_groups = AllGroups::new(Config::default().writeable(true)).unwrap_or_exit(1);
sys_groups.remove_by_name(group.to_string());
sys_groups.save().unwrap_or_exit(1);
}
-81
View File
@@ -1,81 +0,0 @@
#[macro_use]
extern crate clap;
use std::process::exit;
use extra::option::OptionalExt;
use redox_users::{All, AllGroups, AllUsers, Config};
const _MAN_PAGE: &'static str = /* @MANSTART{groupmod} */
r#"
NAME
groupmod - modify group information
SYNOPSYS
groupmod [ options ] GROUP
groupmod [ -h | --help ]
DESCRIPTION
groupmod modifies a user group GROUP in the system's
redox_users backend.
OPTIONS
-h, --help
Print this help page and exit.
-g, --gid GID
Change GROUP's group id. GID must be a non-negative
decimal integer.
Files with GROUP's old gid will not be updated.
User's who use the old gid as their primary gid will
be updated.
-n, --name NAME
The name of the group will be set to NAME
AUTHORS
Wesley Hershberger.
"#; /* @MANEND */
fn main() {
let args = clap_app!(groupmod =>
(author: "Wesley Hershberger")
(about: "Modify users according to the system's redox_users backend")
(@arg GROUP: +required "Modify GROUP")
(@arg GID: -g --gid +takes_value "Change GROUP's group id. See man page for details")
(@arg NAME: -n --name +takes_value "Change GROUP's name")
)
.get_matches();
let groupname = args.value_of("GROUP").unwrap();
let mut sys_groups = AllGroups::new(Config::default().writeable(true)).unwrap_or_exit(1);
{
let group = sys_groups.get_mut_by_name(groupname).unwrap_or_else(|| {
eprintln!("groupmod: group not found: {}", groupname);
exit(1);
});
if let Some(gid) = args.value_of("GID") {
let gid = gid.parse::<usize>().unwrap_or_exit(1);
// Update users
let mut sys_users =
AllUsers::authenticator(Config::default().writeable(true)).unwrap_or_exit(1);
for user in sys_users.iter_mut() {
if user.gid == group.gid {
user.gid = gid;
}
}
sys_users.save().unwrap_or_exit(1);
group.gid = gid;
}
if let Some(name) = args.value_of("NAME") {
group.group = name.to_string();
}
}
sys_groups.save().unwrap_or_exit(1);
}
-163
View File
@@ -1,163 +0,0 @@
#[macro_use]
extern crate clap;
use std::env::args;
use std::process::exit;
use extra::option::OptionalExt;
use redox_users::{All, AllGroups, AllUsers, Config, get_egid, get_euid, get_gid, get_uid};
const _MAN_PAGE: &'static str = /* @MANSTART{id} */
r#"
NAME
id - display user identity
SYNOPSIS
id
id -g [-nr]
id -u [-nr]
id [ -h | --help ]
DESCRIPTION
The id utility displays the user and group names and numeric IDs, of
the calling process, to the standard output.
OPTIONS
-G, --groups
Display the different group IDs (effective and real) as white-space
separated numbers, in no particular order.
-g, --group
Display the effective group ID as a number.
-n, --name
Display the name of the user or group ID for the -g and -u options
instead of the number.
-u, --user
Display the effective user ID as a number.
-a
Ignored for compatibility with other id implementations.
-r, --real
Display the real ID for the -g and -u options instead of the effective ID.
-h, --help
Display help and exit.
AUTHOR
Written by Jose Narvaez.
"#; /* @MANEND */
pub fn main() {
let app = clap_app!(id =>
(author: "Jose Narvaez")
(about: "Get user and group information about the current user")
(@arg IGNORE: -a "Ignored for compatibility with other impls of id")
(@arg GROUPS: -G --groups conflicts_with[selector modifier] "Display current user's real and effective group id's")
(@group selector =>
(@arg GROUP: -g --group "Display current user's effective group id")
(@arg USER: -u --user "Display the effective userid")
)
(@group modifier =>
(@arg NAME: -n --name requires[selector] "Display names of groups/users instead of ids (use with -g or -u)")
(@arg REAL: -r --real requires[selector] "Display real id's instead of effective ids (use with -g and -u)")
)
);
let args = match &*args().nth(0).unwrap_or(String::new()) {
"whoami" => app.get_matches_from(["id", "-un"].iter()),
_ => app.get_matches(),
};
// Display the different group IDs (effective and real)
// as white-space separated numbers, in no particular order.
if args.is_present("GROUPS") {
let egid = get_egid().unwrap_or_exit(1);
let gid = get_gid().unwrap_or_exit(1);
println!("{} {}", egid, gid);
exit(0);
}
// Display effective/real process user ID UNIX user name
if args.is_present("USER") && args.is_present("NAME") {
// Did they pass -r? If so, we show the real
let uid = if args.is_present("REAL") {
get_uid()
} else {
get_euid()
}
.unwrap_or_exit(1);
let users = AllUsers::basic(Config::default()).unwrap_or_exit(1);
let user = users.get_by_id(uid).unwrap_or_exit(1);
println!("{}", user.user);
exit(0);
}
// Display real user ID
if args.is_present("USER") && args.is_present("REAL") {
let uid = get_uid().unwrap_or_exit(1);
println!("{}", uid);
exit(0);
}
// Display effective user ID
if args.is_present("USER") {
let euid = get_euid().unwrap_or_exit(1);
println!("{}", euid);
exit(0);
}
// Display effective/real process group ID UNIX group name
if args.is_present("GROUP") && args.is_present("NAME") {
// Did they pass -r? If so we show the real one
let gid = if args.is_present("REAL") {
get_gid()
} else {
get_egid()
}
.unwrap_or_exit(1);
let groups = AllGroups::new(Config::default()).unwrap_or_exit(1);
let group = groups.get_by_id(gid).unwrap_or_exit(1);
println!("{}", group.group);
exit(0);
}
// Display the real group ID
if args.is_present("GROUP") && args.is_present("REAL") {
let gid = get_gid().unwrap_or_exit(1);
println!("{}", gid);
exit(0);
}
// Display effective group ID
if args.is_present("GROUP") {
let egid = get_egid().unwrap_or_exit(1);
println!("{}", egid);
exit(0);
}
// We get everything we can and show
let euid = get_euid().unwrap_or_exit(1);
let egid = get_egid().unwrap_or_exit(1);
let users = AllUsers::basic(Config::default()).unwrap_or_exit(1);
let groups = AllGroups::new(Config::default()).unwrap_or_exit(1);
let user = users.get_by_id(euid).unwrap_or_exit(1);
let group = groups.get_by_id(egid).unwrap_or_exit(1);
println!("uid={}({}) gid={}({})", euid, user.user, egid, group.group);
exit(0);
}
-204
View File
@@ -1,204 +0,0 @@
#[macro_use]
extern crate clap;
use libredox::error::Result;
use std::fs::File;
use std::io::{self, Write};
use std::str;
use extra::option::OptionalExt;
use redox_users::{All, AllUsers, Config, User};
use termion::input::TermRead;
use userutils::spawn_shell;
const _MAN_PAGE: &'static str = /* @MANSTART{login} */
r#"
NAME
login - log into the computer
SYNOPSIS
login
DESCRIPTION
The login utility logs users (and pseudo-users) into the computer system.
OPTIONS
-h --help
Display help info and exit.
AUTHOR
Written by Jeremy Soller, Jose Narvaez.
"#; /* @MANEND */
const ISSUE_FILE: &'static str = "/etc/issue";
const MOTD_FILE: &'static str = "/etc/motd";
// TODO: Move to redox_users once the definition solidifies.
const DEFAULT_SCHEMES: [&'static str; 26] = [
// Kernel schemes
"debug",
"event",
"memory",
"pipe",
"serio",
"irq",
"time",
"sys",
// Base schemes
"rand",
"null",
"zero",
"log",
// Network schemes
"ip",
"icmp",
"tcp",
"udp",
// IPC schemes
"shm",
"chan",
"uds_stream",
"uds_dgram",
// File schemes
"file",
// Display schemes
"display.vesa",
"display*",
// Other schemes
"pty",
"sudo",
"audio",
];
pub fn apply_login_schemes(
user: &User<redox_users::auth::Full>,
default_schemes: &[&str],
) -> Result<libredox::Fd> {
let schemes = match load_config_schemes(user) {
Some(s) => s,
_ => default_schemes.iter().map(|s| s.to_string()).collect(),
};
let mut names: Vec<ioslice::IoSlice> = Vec::with_capacity(schemes.len());
for scheme in schemes.iter() {
names.push(ioslice::IoSlice::new(scheme.as_bytes()));
}
let ns_fd = libredox::call::mkns(&names)?;
let before_ns_fd = libredox::Fd::new(libredox::call::setns(ns_fd)?);
Ok(before_ns_fd)
}
fn load_config_schemes(user: &User<redox_users::auth::Full>) -> Option<Vec<String>> {
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fs;
const LOGIN_SCHEMES_FILE: &'static str = "/etc/login_schemes.toml";
#[derive(Debug, Clone, Serialize, Deserialize)]
struct UserSchemeConfig {
pub schemes: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct LoginConfig {
#[serde(rename = "user_schemes")]
pub user_schemes: BTreeMap<String, UserSchemeConfig>,
}
let config_str = fs::read_to_string(LOGIN_SCHEMES_FILE).ok()?;
let config: LoginConfig = toml::from_str(&config_str).ok()?;
config
.user_schemes
.get(&user.user)
.map(|cfg| cfg.schemes.clone())
}
pub fn main() {
let mut stdout = io::stdout();
let mut stderr = io::stderr();
let _args = clap_app!(login =>
(author: "Jeremy Soller, Jose Narvaez")
(about: "Login as a user")
)
.get_matches();
if let Ok(mut issue) = File::open(ISSUE_FILE) {
io::copy(&mut issue, &mut stdout).r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
}
loop {
let user = liner::Context::new()
.read_line(
liner::Prompt::from("\x1B[1mRed Bear login:\x1B[0m "),
None,
&mut liner::BasicCompleter::new(Vec::<String>::new()),
)
.r#try(&mut stderr);
if !user.is_empty() {
let stdin = io::stdin();
let mut stdin = stdin.lock();
let sys_users = AllUsers::authenticator(Config::default()).unwrap_or_exit(1);
match sys_users.get_by_name(user) {
None => {
stdout.write(b"\nLogin incorrect\n").r#try(&mut stderr);
stdout.write(b"\n").r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
continue;
}
Some(user) => {
if user.is_passwd_blank() {
if let Ok(mut motd) = File::open(MOTD_FILE) {
io::copy(&mut motd, &mut stdout).r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
}
let before_ns_fd =
apply_login_schemes(user, &DEFAULT_SCHEMES).unwrap_or_exit(1);
let _ = syscall::fcntl(
before_ns_fd.raw(),
syscall::F_SETFD,
syscall::O_CLOEXEC,
);
spawn_shell(user).unwrap_or_exit(1);
let _ = syscall::fcntl(before_ns_fd.raw(), syscall::F_SETFD, 0);
let _ = libredox::call::close(
libredox::call::setns(before_ns_fd.into_raw()).unwrap_or_exit(1),
);
break;
}
stdout
.write_all(b"\x1B[1mpassword:\x1B[0m ")
.r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
if let Some(password) = stdin.read_passwd(&mut stdout).r#try(&mut stderr) {
stdout.write(b"\n").r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
if user.verify_passwd(&password) {
if let Ok(mut motd) = File::open(MOTD_FILE) {
io::copy(&mut motd, &mut stdout).r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
}
spawn_shell(user).unwrap_or_exit(1);
break;
}
}
}
}
} else {
stdout.write(b"\n").r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
}
}
}
-197
View File
@@ -1,197 +0,0 @@
#[macro_use]
extern crate clap;
use std::io;
use std::io::Write;
use std::process::exit;
use extra::option::OptionalExt;
use libredox::flag::O_CLOEXEC;
use libredox::errno::EPERM;
use redox_users::{All, AllUsers, Config, get_uid};
use termion::input::TermRead;
const _MAN_PAGE: &'static str = /* @MANSTART{passwd} */
r#"
NAME
passwd - modify a user's password
SYNOPSIS
passwd [ LOGIN ]
passwd [ -h | --help ]
DESCRIPTION
The passwd utility changes the user's local password. If the user is not
the super-user, passwd first prompts for the current password and will
not continue unless the correct password is entered.
OPTIONS
-h, --help
Display this help and exit.
-l, --lock
Lock the password of the named account. This changes the stored password
hash so that it matches no encrypted value ("!")
Users with locked passwords are not allowed to change their password.
AUTHOR
Written by Jeremy Soller, Jose Narvaez.
"#; /* @MANEND */
fn main() {
let mut stdin = io::stdin().lock();
let mut stdout = io::stdout().lock();
let mut stderr = io::stderr();
let args = clap_app!(passwd =>
(author: "Jeremy Soller, Jose Narvaez")
(about: "Set user passwords")
(@arg LOGIN: "Apply to login. Sets password for current user if not supplied")
(@arg LOCK: -l --lock "Lock the password for an account (no login)")
)
.get_matches();
if args.is_present("LOCK") {
if get_uid().unwrap_or_exit(1) != 0 {
eprintln!("passwd: only root is allowed to lock accounts");
exit(1);
}
let mut users =
AllUsers::authenticator(Config::default().writeable(true)).unwrap_or_exit(1);
let Some(login) = args.value_of("LOGIN") else {
eprintln!("passwd: no account specified to lock");
exit(1);
};
let user = users.get_mut_by_name(login).unwrap_or_else(|| {
eprintln!("passwd: user does not exist: {}", login);
exit(1);
});
user.unset_passwd();
users.save().unwrap_or_exit(1);
return;
}
let uid = get_uid().unwrap_or_exit(1);
if uid == 0 {
let mut users =
AllUsers::authenticator(Config::default().writeable(true)).unwrap_or_exit(1);
let user = find_user(&args, &mut users);
let msg = format!("changing password for '{}' \n", user.user);
stdout.write_all(&msg.as_bytes()).r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
let new_password = ask_new_password(stdin, stdout, stderr);
user.set_passwd(&new_password).unwrap_or_exit(1);
users.save().unwrap_or_exit(1);
return;
}
let mut users = AllUsers::basic(Config::default()).unwrap_or_exit(1);
let user = find_user(&args, &mut users);
if user.uid != uid {
eprintln!(
"passwd: you do not have permission to set the password of '{}'",
user.user
);
exit(1);
}
let msg = format!("changing password for '{}' \n", user.user);
stdout.write_all(&msg.as_bytes()).r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
drop(users); // Unlock /etc/passwd
stdout.write_all(b"current password: ").r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
let file = libredox::call::open("/scheme/sudo/passwd", O_CLOEXEC, 0).unwrap();
if let Some(password) = stdin.read_passwd(&mut stdout).r#try(&mut stderr) {
stdout.write(b"\n").r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
match libredox::call::write(file, password.as_bytes()) {
Ok(_) => {}
Err(err) if err.errno() == EPERM => {
eprintln!("passwd: incorrect current password");
exit(1);
}
Err(err) => panic!("{err}"),
}
} else {
eprintln!("passwd: incorrect current password");
exit(1);
}
let new_password = ask_new_password(stdin, stdout, stderr);
match libredox::call::write(file, new_password.as_bytes()) {
Ok(_) => {}
Err(err) if err.errno() == EPERM => {
eprintln!("passwd: invalid new password");
exit(1);
}
Err(err) => panic!("{err}"),
}
}
fn find_user<'a, T: Default>(
args: &clap::ArgMatches<'_>,
users: &'a mut AllUsers<T>,
) -> &'a mut redox_users::User<T> {
let uid = get_uid().unwrap_or_exit(1);
match args.value_of("LOGIN") {
Some(login) => users.get_mut_by_name(login).unwrap_or_else(|| {
eprintln!("passwd: user does not exist: {}", login);
exit(1);
}),
None => users.get_mut_by_id(uid).unwrap_or_else(|| {
eprintln!("passwd: you do not exist");
exit(1);
}),
}
}
fn ask_new_password(
mut stdin: io::StdinLock<'_>,
mut stdout: io::StdoutLock<'_>,
mut stderr: io::Stderr,
) -> String {
stdout.write_all(b"new password: ").r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
let Some(new_password) = stdin.read_passwd(&mut stdout).r#try(&mut stderr) else {
eprintln!("passwd: no new password provided");
exit(1);
};
stdout.write(b"\nconfirm password: ").r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
let Some(confirm_password) = stdin.read_passwd(&mut stdout).r#try(&mut stderr) else {
eprintln!("\npasswd: no confirm password provided");
exit(1);
};
stdout.write(b"\n").r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
if new_password != confirm_password {
eprintln!("passwd: new password does not match confirm password");
exit(1);
}
new_password
}
-84
View File
@@ -1,84 +0,0 @@
#[macro_use]
extern crate clap;
use std::io::{self, Write};
use std::process::exit;
use std::str;
use extra::option::OptionalExt;
use libredox::flag::O_CLOEXEC;
use redox_users::{All, AllUsers, Config, get_uid};
use syscall::EPERM;
use termion::input::TermRead;
use userutils::spawn_shell;
const _MAN_PAGE: &'static str = /* @MANSTART{su} */
r#"
NAME
su - substitute user identity
SYNOPSIS
su [ user ]
su [ -h | --help ]
DESCRIPTION
The su utility requests appropriate user credentials via PAM and switches to
that user ID (the default user is the superuser). A shell is then executed.
OPTIONS
-h, --help
Display this help and exit.
AUTHOR
Written by Jeremy Soller, Jose Narvaez.
"#; /* @MANEND */
pub fn main() {
let stdin = io::stdin();
let mut stdin = stdin.lock();
let stdout = io::stdout();
let mut stdout = stdout.lock();
let mut stderr = io::stderr();
let args = clap_app!(su =>
(author: "Jeremy Soller, Jose Narvaez")
(about: "substitue user identity")
(@arg LOGIN: "Login as LOGIN. Default is \'root\'")
)
.get_matches();
let target_user = args.value_of("LOGIN").unwrap_or("root");
let uid = get_uid().unwrap_or_exit(1);
let users = AllUsers::basic(Config::default()).unwrap_or_exit(1);
let user = users.get_by_name(&target_user).unwrap_or_exit(1);
// If the user executing su is root, then they can do anything without a password.
// Same if the user we're being asked to login as doesn't have a password.
if uid == 0 {
writeln!(stdout).unwrap_or_exit(1);
exit(spawn_shell(user).unwrap_or_exit(1));
} else {
let file = libredox::call::open("/scheme/sudo/su", O_CLOEXEC, 0).unwrap();
write!(stdout, "password: ").unwrap_or_exit(1);
stdout.flush().unwrap_or_exit(1);
// Read the password, reading an empty string if CTRL-d is specified
let password = stdin
.read_passwd(&mut stdout)
.r#try(&mut stderr)
.unwrap_or(String::new());
match libredox::call::write(file, password.as_bytes()) {
Ok(_) => exit(spawn_shell(user).unwrap_or_exit(1)),
Err(err) if err.errno() == EPERM => {
writeln!(stderr, "su: authentication failed").unwrap_or_exit(1);
exit(1);
}
Err(err) => panic!("{err}"),
}
}
}
-402
View File
@@ -1,402 +0,0 @@
use std::collections::HashMap;
use std::env;
use std::io::{self, Write};
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::os::unix::process::CommandExt;
use std::process::{Command, exit};
use extra::option::OptionalExt;
use libredox::flag::O_CLOEXEC;
use libredox::protocol::ProcCall;
use redox_rt::sys::proc_call;
use redox_scheme::scheme::{SchemeSync, register_sync_scheme};
use redox_scheme::{
CallerCtx, OpenResult, RequestKind, Response, SendFdRequest, SignalBehavior, Socket,
};
use redox_users::{All, AllGroups, AllUsers, Config, get_uid};
use syscall::error::*;
use syscall::flag::*;
use syscall::schemev2::NewFdFlags;
use termion::input::TermRead;
const MAX_ATTEMPTS: u16 = 3;
const _MAN_PAGE: &'static str = /* @MANSTART{sudo} */
r#"
NAME
sudo - execute a command as another user
SYNOPSIS
sudo command
sudo [ -h | --help ]
DESCRIPTION
The sudo utility allows a permitted user to execute a command as the
superuser or another user, as specified by the security policy.
EXIT STATUS
Upon successful execution of a command, the exit status from sudo will
be the exit status of the program that was executed. In case of error
the exit status will be >0.
AUTHOR
Written by Jeremy Soller, Jose Narvaez, bjorn3.
"#; /* @MANEND */
fn main() {
if env::args().nth(1).as_deref() == Some("--daemon") {
daemon_main();
}
let mut args = env::args().skip(1);
let cmd = args.next().unwrap_or_else(|| {
eprintln!("sudo: no command provided");
exit(1);
});
let users = AllUsers::basic(Config::default()).unwrap_or_exit(1);
let uid = get_uid().unwrap_or_exit(1);
let user = users.get_by_id(uid).unwrap_or_exit(1);
if uid == 0 {
// We are root already. No need to elevate privileges
run_command_as_root(&cmd, &args.collect());
}
let file = libredox::call::open("/scheme/sudo", O_CLOEXEC, 0).unwrap();
let mut attempts = 0;
loop {
print!("[sudo] password for {}: ", user.user);
let _ = io::stdout().flush();
match io::stdin().read_passwd(&mut io::stdout()).unwrap() {
Some(password) => {
println!();
match libredox::call::write(file, password.as_bytes()) {
Ok(_) => break,
Err(err) if err.errno() == EPERM => {
attempts += 1;
eprintln!(
"sudo: incorrect password or not in sudo group ({}/{})",
attempts, MAX_ATTEMPTS,
);
if attempts >= MAX_ATTEMPTS {
exit(1);
}
}
Err(err) => panic!("{err}"),
}
}
None => {
println!();
exit(1);
}
}
}
// FIXME move to libredox
unsafe extern "C" {
safe fn redox_cur_procfd_v0() -> usize;
}
// Elevate privileges of our own process with help from the sudo daemon
syscall::sendfd(
file,
syscall::dup(redox_cur_procfd_v0(), &[]).unwrap(),
0,
0,
)
.unwrap();
// FIXME perhaps keep the original namespace available in a subdirectory of the namespace we switch to?
let ns = syscall::openat(file, "ns", 0, syscall::O_CLOEXEC).unwrap();
libredox::call::setns(ns).unwrap();
run_command_as_root(&cmd, &args.collect());
}
enum Policy {
Deny,
Authenticate,
}
fn policy_for_user(uid: u32) -> Policy {
let users = AllUsers::authenticator(Config::default()).unwrap_or_exit(1);
let groups = AllGroups::new(Config::default()).unwrap_or_exit(1);
let user = users.get_by_id(uid as usize).unwrap_or_exit(1);
let sudo_group = groups.get_by_name("sudo").unwrap_or_exit(1);
if !sudo_group.users.iter().any(|name| name == &user.user) {
return Policy::Deny;
}
Policy::Authenticate
}
fn run_command_as_root(cmd: &str, args: &Vec<String>) -> ! {
let mut command = Command::new(&cmd);
for arg in args {
command.arg(&arg);
}
command.uid(0);
command.gid(0);
command.env("USER", "root");
command.env("UID", "0");
command.env("GROUPS", "0");
let err = command.exec();
eprintln!("sudo: failed to execute {}: {}", cmd, err);
exit(1);
}
struct Scheme {
next_fd: usize,
handles: HashMap<usize, Handle>,
}
enum Handle {
AwaitingPassword { uid: u32 },
AwaitingRootPassword,
AwaitingContextFd,
AwaitingNamespaceFetch { ns: libredox::Fd },
AwaitingPasswordForPasswd { uid: u32 },
AwaitingNewPassword { uid: u32 },
Placeholder,
SchemeRoot,
}
impl SchemeSync for Scheme {
fn scheme_root(&mut self) -> Result<usize> {
let fd = self.next_fd;
self.next_fd = self.next_fd.checked_add(1).ok_or(Error::new(EMFILE))?;
self.handles.insert(fd, Handle::SchemeRoot);
Ok(fd)
}
fn openat(
&mut self,
dirfd: usize,
path: &str,
_flags: usize,
_fcntl_flags: u32,
ctx: &CallerCtx,
) -> Result<OpenResult> {
let handle = match self.handles.get_mut(&dirfd).ok_or(Error::new(EBADF))? {
Handle::SchemeRoot => match path {
"" => Handle::AwaitingPassword { uid: ctx.uid },
"su" => Handle::AwaitingRootPassword,
"passwd" => Handle::AwaitingPasswordForPasswd { uid: ctx.uid },
_ => return Err(Error::new(ENOENT)),
},
Handle::AwaitingNamespaceFetch { .. } => {
if path != "ns" {
return Err(Error::new(ENOENT));
}
let ns = match self.handles.insert(dirfd, Handle::Placeholder).unwrap() {
Handle::AwaitingNamespaceFetch { ns } => ns,
_ => unreachable!(),
};
return Ok(OpenResult::OtherScheme { fd: ns.into_raw() });
}
_ => return Err(Error::new(EINVAL)),
};
let fd = self.next_fd;
self.next_fd = self.next_fd.checked_add(1).ok_or(Error::new(EMFILE))?;
self.handles.insert(fd, handle);
Ok(OpenResult::ThisScheme {
number: fd,
flags: NewFdFlags::empty(),
})
}
fn write(
&mut self,
id: usize,
buf: &[u8],
_off: u64,
_flags: u32,
_ctx: &CallerCtx,
) -> Result<usize> {
let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?;
let validate_utf8 = |buf| std::str::from_utf8(buf).map_err(|_| Error::new(EINVAL));
match std::mem::replace(handle, Handle::Placeholder) {
Handle::AwaitingPassword { uid } => {
let users = AllUsers::authenticator(Config::default()).unwrap_or_exit(1);
let user = users.get_by_id(uid as usize).unwrap_or_exit(1);
match policy_for_user(uid) {
Policy::Deny => {
*handle = Handle::AwaitingPassword { uid };
return Err(Error::new(EPERM));
}
Policy::Authenticate => {
let password = validate_utf8(buf)?;
if user.verify_passwd(&password) {
*handle = Handle::AwaitingContextFd
} else {
*handle = Handle::AwaitingPassword { uid };
return Err(Error::new(EPERM));
}
}
}
}
Handle::AwaitingRootPassword => {
let users = AllUsers::authenticator(Config::default()).unwrap_or_exit(1);
let user = users.get_by_id(0).unwrap_or_exit(1);
let password = validate_utf8(buf)?;
if user.verify_passwd(&password) {
*handle = Handle::AwaitingContextFd
} else {
*handle = Handle::AwaitingRootPassword;
return Err(Error::new(EPERM));
}
}
Handle::AwaitingContextFd => {
*handle = Handle::AwaitingContextFd;
return Err(Error::new(EINVAL));
}
Handle::AwaitingPasswordForPasswd { uid } => {
let users =
AllUsers::authenticator(Config::default()).map_err(|_| Error::new(ENOLCK))?;
let user = users.get_by_id(uid as usize).ok_or(Error::new(EEXIST))?;
let password = validate_utf8(buf)?;
if user.verify_passwd(&password) {
*handle = Handle::AwaitingNewPassword { uid }
} else {
*handle = Handle::AwaitingPasswordForPasswd { uid };
return Err(Error::new(EPERM));
}
}
Handle::AwaitingNewPassword { uid } => {
let mut users = AllUsers::authenticator(Config::default().writeable(true))
.map_err(|_| Error::new(ENOLCK))?;
let user = users
.get_mut_by_id(uid as usize)
.ok_or(Error::new(EEXIST))?;
let new_password = validate_utf8(buf)?;
if user.set_passwd(&new_password).is_ok() {
users.save().map_err(|_| Error::new(ENOLCK))?;
*handle = Handle::Placeholder
} else {
*handle = Handle::AwaitingNewPassword { uid };
return Err(Error::new(EPERM));
}
}
Handle::AwaitingNamespaceFetch { .. } => {
eprintln!("sudo: found namespace fetch handle with ID {id}");
return Err(Error::new(EBADFD));
}
Handle::Placeholder => {
eprintln!("sudo: found placeholder handle with ID {id}");
return Err(Error::new(EBADFD));
}
Handle::SchemeRoot => {
eprintln!("sudo: found Scheme root handle with ID {id}");
return Err(Error::new(EBADFD));
}
}
Ok(buf.len())
}
}
impl Scheme {
fn on_close(&mut self, id: usize) {
self.handles.remove(&id);
}
fn on_sendfd(&mut self, socket: &Socket, req: &SendFdRequest) -> Result<usize> {
let handle = self.handles.get_mut(&req.id()).ok_or(Error::new(EBADF))?;
match std::mem::replace(handle, Handle::Placeholder) {
Handle::AwaitingContextFd => {
let mut proc_fd = usize::MAX;
req.obtain_fd(
socket,
FobtainFdFlags::empty(),
std::slice::from_mut(&mut proc_fd),
)?;
let proc_fd = unsafe { OwnedFd::from_raw_fd(proc_fd as RawFd) };
let [ruid, euid, suid] = [0, 0, 0];
let [rgid, egid, sgid] = [0, 0, 0];
let mut payload = [0; size_of::<u32>() * 6];
plain::slice_from_mut_bytes(&mut payload)
.unwrap()
.copy_from_slice(&[ruid, euid, suid, rgid, egid, sgid]);
if let Err(err) = proc_call(
proc_fd.as_raw_fd() as usize,
&mut payload,
CallFlags::empty(),
&[ProcCall::SetResugid as u64],
) {
eprintln!("failed to setresugid: {err}");
}
*handle = Handle::AwaitingNamespaceFetch {
ns: libredox::Fd::new(
syscall::dup(libredox::call::getns().unwrap(), b"").unwrap(),
),
};
}
old => {
*handle = old;
return Err(Error::new(EBADF));
}
}
Ok(0)
}
}
fn daemon_main() -> ! {
// TODO: Linux kernel audit-like logging?
let socket = Socket::create().expect("failed to open scheme socket");
let mut scheme = Scheme {
next_fd: 1,
handles: HashMap::new(),
};
let mut scheme_state = redox_scheme::scheme::SchemeState::new();
register_sync_scheme(&socket, "sudo", &mut scheme)
.expect("failed to register sudo scheme to namespace");
loop {
let Some(req) = socket
.next_request(SignalBehavior::Restart)
.expect("failed to get request")
else {
break;
};
let response = match req.kind() {
RequestKind::Call(call) => call.handle_sync(&mut scheme, &mut scheme_state),
RequestKind::SendFd(req) => Response::new(scheme.on_sendfd(&socket, &req), req),
RequestKind::OnClose { id } => {
scheme.on_close(id);
continue;
}
_ => continue,
};
socket
.write_response(response, SignalBehavior::Restart)
.expect("sudo: scheme write failed");
}
std::process::exit(0)
}
-205
View File
@@ -1,205 +0,0 @@
#[macro_use]
extern crate clap;
use std::process::exit;
use extra::option::OptionalExt;
use redox_users::{All, AllGroups, AllUsers, Config, GroupBuilder, UserBuilder};
use userutils::create_user_dir;
const _MAN_PAGE: &'static str = /* @MANSTART{useradd} */
r#"
NAME
useradd - add a new user
SYNOPSYS
useradd [ options ] LOGIN
useradd [ -h | --help ]
DESCRIPTION
The useradd utility creates a new user based on
system defaults and values passed on the command line.
Useradd creates a new group for the user by default and
can also be instructed to create the user's home directory.
Note that useradd creates a new user with the password
unset (no login). This is better documented with the
redox_users crate.
OPTIONS
-h, --help
Display this help and exit.
-c, --comment
Any text string, usually used as the user's full name.
Historically known as the GECOS field
-d, --home-dir HOME_DIR
The new user will be created with HOME_DIR as their home
directory. The default value is LOGIN prepended with "/home/".
This flag DOES NOT create the home directory. See --create-home
-g, --gid GID
The group id to use when creating the default login group. This value
must not be in use and must be non-negative. The default is to pick the
smallest available group id between values defined in redox_users.
-m, --create-home
Creates the user's home directory if it does not already exist.
This option is not enabled by default. This option must be specified
for a home directory to be created. If not set, the user's home dir is
set to "/"
-N, --no-user-group
Do not attempt to create the user's user group. Instead, the groupid
is set to 99 ("nobody"). -N and -g are mutually exclusive.
-s, --shell SHELL
The path to the user's default login shell. If not specified, the
default shell is set as "/bin/ion"
-u, --uid UID
The user id to use. This value must not be in use and must be
non-negative. The default is to pick the smallest available
user id between the defaults defined in redox_users
AUTHORS
Written by Wesley Hershberger.
"#; /* @MANEND */
const DEFAULT_SHELL: &'static str = "/bin/ion";
const DEFAULT_HOME: &'static str = "/home";
const DEFAULT_NO_GROUP: &'static str = "nobody";
fn main() {
let args = clap_app!(useradd =>
(author: "Wesley Hershberger")
(about: "Add users based on the system's redox_users backend")
(@arg LOGIN:
+required
"Add user LOGIN")
(@arg COMMENT:
-c --comment
+takes_value
"Set user description (GECOS field)")
(@arg HOME_DIR:
-d --("home-dir")
+takes_value
"Set LOGIN's home dir to HOME_DIR (does not create directory)")
(@arg CREATE_HOME:
-m --("create-home")
"Create the user's home directory")
(@arg SHELL:
-s --shell
+takes_value
"Set user's default login shell")
(@arg GID:
-g --gid
+takes_value
"Set LOGIN's primary group id. Positive integer and must not be in use.")
(@arg NO_USER_GROUP:
-N --("no-user-group")
conflicts_with[GID]
"Do not create primary user group (set gid to 99, \"nobody\")")
(@arg UID:
-u --uid
+takes_value
"Set LOGIN's user id. Positive ineger and must not be in use.")
)
.get_matches();
// unwrap is safe because of "+required". clap-rs is cool...
let login = args.value_of("LOGIN").unwrap();
let mut sys_users =
AllUsers::authenticator(Config::default().writeable(true)).unwrap_or_exit(1);
let mut sys_groups = AllGroups::new(Config::default().writeable(true)).unwrap_or_exit(1);
let uid = match args.value_of("UID") {
Some(uid) => {
let id = uid.parse::<usize>().unwrap_or_exit(1);
if let Some(_user) = sys_users.get_by_id(id) {
eprintln!("useradd: userid already in use: {}", id);
exit(1);
}
id
}
None => sys_users.get_unique_id().unwrap_or_else(|| {
eprintln!("useradd: no available uid");
exit(1);
}),
};
let gid = if args.is_present("NO_USER_GROUP") {
let nobody = sys_groups
.get_mut_by_name(DEFAULT_NO_GROUP)
.unwrap_or_else(|| {
eprintln!("useradd: group not found: {}", DEFAULT_NO_GROUP);
exit(1)
});
nobody.users.push(login.to_string());
99
} else {
let id = match args.value_of("GID") {
Some(id) => {
let id = id.parse::<usize>().unwrap_or_exit(1);
if let Some(_group) = sys_groups.get_by_id(id) {
eprintln!("useradd: group already exists with gid: {}", id);
exit(1);
}
id
}
None => sys_groups.get_unique_id().unwrap_or_else(|| {
eprintln!("useradd: no available gid");
exit(1);
}),
};
sys_groups
.add_group(GroupBuilder::new(login).gid(id).user(login))
.unwrap_or_else(|err| {
eprintln!("useradd: {}: {}", err, login);
exit(1);
});
id
};
let gecos = args.value_of("COMMENT").unwrap_or(login);
//Ugly way to satisfy the borrow checker...
let mut sys_homes = String::from(DEFAULT_HOME);
let userhome = args.value_of("HOME_DIR").unwrap_or_else(|| {
if args.is_present("CREATE_HOME") {
sys_homes.push_str("/");
sys_homes.push_str(&login);
sys_homes.as_str()
} else {
"/"
}
});
let shell = args.value_of("SHELL").unwrap_or(DEFAULT_SHELL);
let user = UserBuilder::new(login)
.uid(uid)
.gid(gid)
.name(gecos)
.home(userhome)
.shell(shell);
sys_users.add_user(user).unwrap_or_else(|err| {
eprintln!("useradd: {}: {}", err, login);
exit(1);
});
// Make sure to try and create the user/groups before we create
// their home, that way we get a permissions error that makes
// more sense
sys_groups.save().unwrap_or_exit(1);
sys_users.save().unwrap_or_exit(1);
if args.is_present("CREATE_HOME") {
//Shouldn't ever error...
let user = sys_users.get_by_id(uid).unwrap_or_exit(1);
create_user_dir(user, userhome).unwrap_or_exit(1);
}
}
-69
View File
@@ -1,69 +0,0 @@
#[macro_use]
extern crate clap;
use std::fs::remove_dir;
use std::process::exit;
use extra::option::OptionalExt;
use redox_users::{All, AllGroups, AllUsers, Config};
use userutils::AllGroupsExt;
const _MAN_PAGE: &'static str = /* @MANSTART{userdel} */
r#"
NAME
userdel - modify system files to delete users
SYNOPSYS
userdel [ options ] LOGIN
userdel [ -h | --help ]
DESCRIPTION
userdel removes users from whatever backend is employed by
the system's redox_users. The utility removes the user from
all groups of which they are a member.
It can also be used to manage removal of home directories.
OPTIONS
-h, --help
Print this help page and exit.
-r, --remove
The user's home directory and all files inside will be
removed.
AUTHORS
Wesley Hershberger.
"#; /* @MANEND */
fn main() {
let args = clap_app!(userdel =>
(author: "Wesley Hershberger")
(about: "Removes system users using redox_users")
(@arg LOGIN: +required "Remove user LOGIN")
(@arg REMOVE: -r --remove "Remove the user's home and all files and directories inside")
)
.get_matches();
let login = args.value_of("LOGIN").unwrap();
let mut sys_users =
AllUsers::authenticator(Config::default().writeable(true)).unwrap_or_exit(1);
let mut sys_groups = AllGroups::new(Config::default().writeable(true)).unwrap_or_exit(1);
{
sys_groups.remove_user_from_all_groups(login);
if args.is_present("REMOVE") {
let user = sys_users.get_by_name(login).unwrap_or_else(|| {
eprintln!("userdel: user does not exist: {}", login);
exit(1);
});
remove_dir(&user.home).unwrap_or_exit(1);
}
}
sys_users.remove_by_name(login.to_string());
sys_groups.save().unwrap_or_exit(1);
sys_users.save().unwrap_or_exit(1);
}
-196
View File
@@ -1,196 +0,0 @@
#[macro_use]
extern crate clap;
use std::fs::{remove_dir, rename};
use std::process::exit;
use extra::option::OptionalExt;
use redox_users::{All, AllGroups, AllUsers, Config};
use userutils::{AllGroupsExt, create_user_dir};
const _MAN_PAGE: &'static str = /* @MANSTART{usermod} */
r#"
NAME
usermod - modify user information
SYNOPSYS
usermod [ options ] LOGIN
usermod [ -h | --help ]
DESCRIPTION
The usermod utility can be used to modify user information.
This utility uses the redox_users API, so the backend is whatever
backend in use on the system for that API at the time.
See passwd for setting user passwords.
OPTIONS
-h, --help
Display this help and exit.
-c, --comment COMMENT
The comment field (or GECOS, historically) for the user. This
is typically the full name of the user, although sometimes it
includes an e-mail.
-d, --home-dir HOME_DIR
Sets the home directory to HOME_DIR and creates the directory.
See -m for move
-m, --move-home
Moves the the user's old home directory into the home directory
specified by --home-dir. Has no effect if passed without --home-dir
-G, --append-groups GROUP[,GROUP, ...]
Add this user to GROUP groups. This does not remove the user from
any group of which they are already a member.
-S, --set-groups GROUP[,GROUP, ...]
Remove the user from all groups of which they are a part and add
them to GROUP groups.
-g, --gid GID
Set the user's primary group id. If the group does not exist,
a warning is issued and no changes are applied.
-l, --login NEW_LOGIN
Set the new login name for the user. Must not be in use.
-s, --shell SHELL
Set the user's login shell as SHELL. This must be a full path.
-u, --uid UID
Set the user's user id. If another user's userid is the same as
UID, a warning is issued and no changes are applied. Note that
changing the value of the user's userid may have unexpected consequences.
AUTHORS
Written by Wesley Hershberger.
"#; /* @MANEND */
fn main() {
let args = clap_app!(usermod =>
(author: "Wesley Hershberger")
(about: "Modify users according to the system's redox_users backend")
(@arg LOGIN:
+required
"Apply modifications to LOGIN")
(@arg COMMENT:
-c --comment
+takes_value
"Set LOGIN's description (GECOS field)")
(@arg HOME_DIR:
-d --("home-dir")
+takes_value
"Create and set LOGIN's home directory")
(@arg MOVE_HOME:
-m --("move-home")
requires[HOME_DIR]
"Move LOGIN's old home to HOME_DIR (see --home-dir) instead of creating it. Requires -d")
(@arg APPEND_GROUPS:
-G --("append-groups")
+takes_value conflicts_with[SET_GROUPS]
"Add user to groups specified (comma separated list, see man page)")
(@arg SET_GROUPS:
-S --("set-groups")
+takes_value conflicts_with[APPEND_GROUPS]
"Set LOGIN's groups as specified (truncates existing, see man page)")
(@arg GID:
-g --gid
+takes_value
"Set LOGIN's primary group id. Group must exist")
(@arg NEW_LOGIN:
-l --login
+takes_value
"Set LOGIN's name to NEW_LOGIN")
(@arg SHELL:
-s --shell
+takes_value
"Set LOGIN's default login shell")
(@arg UID:
-u --uid
+takes_value
"Set LOGIN's user id. See man page for details")
).get_matches();
let login = args.value_of("LOGIN").unwrap();
//TODO: Does not always need shadowfile access
let mut sys_users =
AllUsers::authenticator(Config::default().writeable(true)).unwrap_or_exit(1);
let mut sys_groups;
if let Some(new_groups) = args.value_of("SET_GROUPS") {
sys_groups = AllGroups::new(Config::default().writeable(true)).unwrap_or_exit(1);
sys_groups.remove_user_from_all_groups(login);
sys_groups
.add_user_to_groups(login, new_groups.split(',').collect())
.unwrap_or_exit(1);
sys_groups.save().unwrap_or_exit(1);
}
if let Some(new_groups) = args.value_of("APPEND_GROUPS") {
sys_groups = AllGroups::new(Config::default().writeable(true)).unwrap_or_exit(1);
sys_groups
.add_user_to_groups(login, new_groups.split(',').collect())
.unwrap_or_exit(1);
sys_groups.save().unwrap_or_exit(1);
}
let uid = args.value_of("UID").map(|uid| {
let uid = uid.parse::<usize>().unwrap_or_exit(1);
if let Some(_user) = sys_users.get_by_id(uid) {
eprintln!("usermod: userid already in use: {}", uid);
exit(1);
}
uid
});
{
let user = sys_users.get_mut_by_name(&login).unwrap_or_else(|| {
eprintln!("usermod: user \"{}\" not found", login);
exit(1);
});
if let Some(gecos) = args.value_of("COMMENT") {
user.name = gecos.to_string();
}
if let Some(new_login) = args.value_of("NEW_LOGIN") {
user.user = new_login.to_string();
}
if let Some(shell) = args.value_of("SHELL") {
user.shell = shell.to_string();
}
if let Some(home) = args.value_of("HOME_DIR") {
if args.is_present("MOVE_HOME") {
rename(&user.home, &home).unwrap_or_exit(1);
} else {
create_user_dir(user, &home).unwrap_or_exit(1);
remove_dir(&user.home).unwrap_or_exit(1);
}
user.home = home.to_string();
}
if let Some(uid) = uid {
user.uid = uid;
}
if let Some(gid) = args.value_of("GID") {
sys_groups = AllGroups::new(Config::default()).unwrap_or_exit(1);
let gid = gid.parse::<usize>().unwrap_or_exit(1);
if let Some(_group) = sys_groups.get_by_id(gid) {
user.gid = gid;
} else {
eprintln!("usermod: no group found for id: {}", gid);
exit(1);
}
}
}
sys_users.save().unwrap_or_exit(1);
}
+1130 -79
View File
File diff suppressed because it is too large Load Diff