Red Bear OS userutils baseline from 0.1.0 pre-patched archive
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/target/
|
||||
@@ -0,0 +1,6 @@
|
||||
language: rust
|
||||
rust:
|
||||
- nightly
|
||||
sudo: false
|
||||
notifications:
|
||||
email: false
|
||||
Generated
+665
@@ -0,0 +1,665 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
|
||||
|
||||
[[package]]
|
||||
name = "blake2b_simd"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec",
|
||||
"constant_time_eq",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytecount"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.34.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"atty",
|
||||
"bitflags 1.3.2",
|
||||
"strsim",
|
||||
"textwrap",
|
||||
"unicode-width",
|
||||
"vec_map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "extra"
|
||||
version = "0.1.0"
|
||||
source = "git+https://gitlab.redox-os.org/redox-os/libextra.git#cf213969493db8667052a591e32a1e26d43c4234"
|
||||
|
||||
[[package]]
|
||||
name = "generic-rt"
|
||||
version = "0.1.0"
|
||||
source = "git+https://gitlab.redox-os.org/redox-os/relibc#3a8f64aa143184dc11fe1927a4f9e55c33ae3052"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "goblin"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f27c1b4369c2cd341b5de549380158b105a04c331be5db9110eef7b6d2742134"
|
||||
dependencies = [
|
||||
"log",
|
||||
"plain",
|
||||
"scroll",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ioslice"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e571352c8a3b89074d12e3ee5173ffe162159105352aaaf1fc5764da747e31b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.176"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"ioslice",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "numtoa"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f"
|
||||
|
||||
[[package]]
|
||||
name = "orbclient"
|
||||
version = "0.3.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"libredox",
|
||||
"sdl2",
|
||||
"sdl2-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plain"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox-path"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "436d45c2b6a5b159d43da708e62b25be3a4a3d5550d654b72216ade4c4bfd717"
|
||||
|
||||
[[package]]
|
||||
name = "redox-rt"
|
||||
version = "0.1.0"
|
||||
source = "git+https://gitlab.redox-os.org/redox-os/relibc#3a8f64aa143184dc11fe1927a4f9e55c33ae3052"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"generic-rt",
|
||||
"goblin",
|
||||
"ioslice",
|
||||
"plain",
|
||||
"redox-path",
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox-scheme"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5eb0cee524a5c6e4db180250f699e101ee25f3f79ad2618527e539e028cb6722"
|
||||
dependencies = [
|
||||
"libredox",
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_event"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c30b73c93693667b5a8e6f54bddcc5a57449ef17c99f4b55c12d5bae4ab79e82"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"libredox",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_liner"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee4596948a78a2ac29268d4ee45f788789ac8c032c77cf255378e5c96ca0c779"
|
||||
dependencies = [
|
||||
"bytecount",
|
||||
"itertools",
|
||||
"strip-ansi-escapes",
|
||||
"termion",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_termios"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb"
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libredox",
|
||||
"rust-argon2",
|
||||
"thiserror",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-argon2"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"blake2b_simd",
|
||||
"constant_time_eq",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scroll"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da"
|
||||
dependencies = [
|
||||
"scroll_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scroll_derive"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sdl2"
|
||||
version = "0.35.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7959277b623f1fb9e04aea73686c3ca52f01b2145f8ea16f4ff30d8b7623b1a"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"sdl2-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sdl2-sys"
|
||||
version = "0.35.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3586be2cf6c0a8099a79a12b4084357aa9b3e0b0d7980e3b67aaf7a9d55f9f0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"version-compare",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strip-ansi-escapes"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025"
|
||||
dependencies = [
|
||||
"vte",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termion"
|
||||
version = "4.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3669a69de26799d6321a5aa713f55f7e2cd37bd47be044b50f2acafc42c122bb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"libredox",
|
||||
"numtoa",
|
||||
"redox_termios",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_write",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_write"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||
|
||||
[[package]]
|
||||
name = "userutils"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"extra",
|
||||
"ioslice",
|
||||
"libc",
|
||||
"libredox",
|
||||
"orbclient",
|
||||
"plain",
|
||||
"redox-rt",
|
||||
"redox-scheme",
|
||||
"redox_event",
|
||||
"redox_liner",
|
||||
"redox_syscall",
|
||||
"redox_termios",
|
||||
"redox_users",
|
||||
"serde",
|
||||
"termion",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
|
||||
|
||||
[[package]]
|
||||
name = "vte"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize_derive"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
[package]
|
||||
name = "userutils"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[[bin]]
|
||||
name = "id"
|
||||
path = "src/bin/id.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "getty"
|
||||
path = "src/bin/getty.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "groupadd"
|
||||
path = "src/bin/groupadd.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "groupdel"
|
||||
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]
|
||||
clap = "2.33.0"
|
||||
extra = { git = "https://gitlab.redox-os.org/redox-os/libextra.git" }
|
||||
orbclient = "0.3.47"
|
||||
plain = "0.2.3"
|
||||
redox_liner = "0.5.2"
|
||||
libredox = { version = "0.1.12", features = ["mkns"] }
|
||||
redox_termios = "0.1.3"
|
||||
redox_event = "0.4.3"
|
||||
redox-scheme = "0.9.0"
|
||||
redox_syscall = "0.7.0"
|
||||
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 }
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 The Redox developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,24 @@
|
||||
# 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.
|
||||
|
||||
[](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
|
||||
@@ -0,0 +1,6 @@
|
||||
########## Red Bear OS #########
|
||||
# Login with the following: #
|
||||
# `user` #
|
||||
# `root`:`password` #
|
||||
################################
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
########## Redox OS ##########
|
||||
# Login with the following: #
|
||||
# `user` #
|
||||
# `root`:`password` #
|
||||
##############################
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
#[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);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
#[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);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#[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);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
#[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
@@ -0,0 +1,163 @@
|
||||
#[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);
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
#[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
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
#[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}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
+401
@@ -0,0 +1,401 @@
|
||||
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 redox_rt::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(),
|
||||
};
|
||||
|
||||
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),
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
#[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);
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
#[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);
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
//! 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.
|
||||
//!
|
||||
//! The included utilities are:
|
||||
//!
|
||||
//! - `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 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.
|
||||
//! - `whoami`: Display effective user ID.
|
||||
|
||||
use std::io::Result as IoResult;
|
||||
|
||||
use libredox::call::{fchown, open};
|
||||
use libredox::error::Result as SysResult;
|
||||
use libredox::flag::{O_CLOEXEC, O_CREAT, O_DIRECTORY};
|
||||
use redox_users::{All, AllGroups, Error, Result, User, auth};
|
||||
|
||||
const DEFAULT_MODE: u16 = 0o700;
|
||||
|
||||
// Not the prettiest thing in the world, but some functionality here makes
|
||||
// some of the utils much less gross
|
||||
pub trait AllGroupsExt {
|
||||
fn add_user_to_groups(&mut self, login: &str, groups: Vec<&str>) -> Result<()>;
|
||||
fn remove_user_from_all_groups(&mut self, login: &str);
|
||||
}
|
||||
|
||||
impl AllGroupsExt for AllGroups {
|
||||
// new_groups is a comma separated list of groupnames
|
||||
fn add_user_to_groups(&mut self, login: &str, new_groups: Vec<&str>) -> Result<()> {
|
||||
for groupname in new_groups {
|
||||
let group = match self.get_mut_by_name(groupname) {
|
||||
Some(group) => group,
|
||||
None => return Err(Error::UserNotFound),
|
||||
};
|
||||
group.users.push(login.to_string());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove a user from all groups of which they are a member
|
||||
fn remove_user_from_all_groups(&mut self, login: &str) {
|
||||
for group in self.iter_mut() {
|
||||
let op_pos = group.users.iter().position(|username| username == login);
|
||||
if let Some(indx) = op_pos {
|
||||
group.users.remove(indx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawns a shell for the given `User`.
|
||||
///
|
||||
/// This function wraps the shell_cmd function of the User struct
|
||||
/// from redox_users and manages the child process. It is a blocking
|
||||
/// operation.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use redox_users::AllUsers;
|
||||
///
|
||||
/// let sys_users = AllUsers::new().unwrap();
|
||||
/// let user = sys_users.get_by_name("goyox86");
|
||||
/// spawn_shell(user).unwrap();
|
||||
/// ```
|
||||
pub fn spawn_shell<T: Default>(user: &User<T>) -> IoResult<i32> {
|
||||
let mut command = user.shell_cmd();
|
||||
|
||||
let mut child = command.spawn()?;
|
||||
match child.wait()?.code() {
|
||||
Some(code) => Ok(code),
|
||||
None => Ok(1),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a directory with 700 user:user permissions
|
||||
pub fn create_user_dir<T>(user: &User<auth::Full>, dir: T) -> SysResult<()>
|
||||
where
|
||||
T: AsRef<str> + std::convert::AsRef<[u8]>,
|
||||
{
|
||||
let fd = open(dir, O_CREAT | O_DIRECTORY | O_CLOEXEC, DEFAULT_MODE)?;
|
||||
fchown(fd, user.uid as u32, user.gid as u32)?;
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user