Merge pull request '0.6.0' (#1) from dev into main

Reviewed-on: https://g.tylerm.dev/tylermurphy534/crab/pulls/1
This commit is contained in:
tylermurphy534 2022-11-19 17:37:56 +00:00
commit ce6af95b74
19 changed files with 657 additions and 376 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
/target
Cargo.lock
test.sh
crab.tar.gz

68
Cargo.lock generated
View file

@ -34,13 +34,12 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "crab"
version = "0.0.5"
version = "0.0.6"
dependencies = [
"exec",
"nix",
"pam",
"procinfo",
"pwd",
"rpassword",
"serde_json",
"time",
@ -144,15 +143,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "proc-macro2"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
dependencies = [
"unicode-ident",
]
[[package]]
name = "procinfo"
version = "0.4.2"
@ -165,25 +155,6 @@ dependencies = [
"rustc_version",
]
[[package]]
name = "pwd"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72c71c0c79b9701efe4e1e4b563b2016dd4ee789eb99badcb09d61ac4b92e4a2"
dependencies = [
"libc",
"thiserror",
]
[[package]]
name = "quote"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rpassword"
version = "7.1.0"
@ -241,37 +212,6 @@ dependencies = [
"serde",
]
[[package]]
name = "syn"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "time"
version = "0.3.17"
@ -288,12 +228,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
[[package]]
name = "unicode-ident"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
[[package]]
name = "users"
version = "0.8.1"

View file

@ -1,11 +1,10 @@
[package]
name = "crab"
version = "0.0.5"
version = "0.0.6"
edition = "2021"
[dependencies]
pam = "0.7.0"
pwd = "1.4.0"
nix = "0.25.0"
exec = "0.3.1"
rpassword = "7.1.0"

1
conf
View file

@ -1 +0,0 @@
root true

5
config/default Normal file
View file

@ -0,0 +1,5 @@
deny :docker
permit nopass persist linus as root
#deny stallman
permit :wheel persist
permit nvidia as fu

View file

View file

@ -1,16 +1,15 @@
pkgbase = crab
pkgdesc = A rusty permission authentication system
pkgver = 0.0.5
pkgver = 0.0.6
pkgrel = 2
url = https://g.tylerm.dev/tylermurphy534/crab.git
url = https://g.tylerm.dev/tylermurphy534/crab
arch = x86_64
arch = i686
license = GPL3
makedepends = git
makedepends = cargo
depends = glibc
depends = pam
source = git+https://g.tylerm.dev/tylermurphy534/crab.git
source = crab-0.0.6.tar.gz::https://f.tylerm.dev/source/crab/crab-0.0.6.tar.gz
md5sums = SKIP
pkgname = crab

View file

@ -1,24 +1,24 @@
# Maintainer: Tyler Murphy <tylermurphy534@gmail.com>
pkgname=crab
pkgver=0.0.5
pkgrel=2
pkgver=0.0.6
pkgrel=1
pkgdesc="A rusty permission authentication system"
arch=('x86_64' 'i686')
url="https://g.tylerm.dev/tylermurphy534/crab.git"
url="https://g.tylerm.dev/tylermurphy534/crab"
license=('GPL3')
makedepends=('git' 'cargo')
makedepends=('cargo')
depends=('glibc' 'pam')
source=("git+$url")
source=("$pkgname-$pkgver.tar.gz::https://f.tylerm.dev/source/$pkgname/$pkgname-$pkgver.tar.gz")
md5sums=('SKIP')
build() {
cd crab
cd "$srcdir"
cargo build --release
}
package() {
cd crab
install -D --mode=6755 --owner=root --group=root ./target/release/crab ${pkgdir}/usr/bin/crab
install -D --mode=660 --owner=root --group=root pam ${pkgdir}/etc/pam.d/crab
install -D --mode=660 --owner=root --group=root conf ${pkgdir}/usr/share/crab/crab.conf
cd "$srcdir"
install -D --mode=4755 --owner=root --group=root ./target/release/crab ${pkgdir}/usr/bin/crab
install -D --mode=600 --owner=root --group=root ./config/pam ${pkgdir}/etc/pam.d/crab
install -D --mode=644 --owner=root --group=root ./config/default ${pkgdir}/usr/share/crab/crab.conf
}

21
deployments/source/install.sh Executable file
View file

@ -0,0 +1,21 @@
#!/bin/bash
if [[ $(/usr/bin/id -u) -ne 0 ]]; then
echo "Please run this as root"
exit
fi
# Copy executable to bin
cp ./target/release/crab /usr/bin/crab
chown root:root /usr/bin/crab
chmod 4755 /usr/bin/crab
# Set up config files
cp ./config/pam /etc/pam.d/crab
chmod 600 /etc/pam.d/crab
mkdir /usr/share/crab
chmod 644 /usr/share/crab
cp ./config/default /usr/share/crab/crab.conf
chmod 644 /usr/share/crab/crab.conf

11
deployments/source/uninstall.sh Executable file
View file

@ -0,0 +1,11 @@
#!/bin/bash
if [[ $(/usr/bin/id -u) -ne 0 ]]; then
echo "Please run this as root"
exit
fi
# Delete crab files
rm /etc/pam.d/crab
rm /usr/bin/crab
rm -fr /usr/share/crab

View file

@ -1,17 +0,0 @@
#!/bin/bash
if [[ $(/usr/bin/id -u) -ne 0 ]]; then
echo "Please run this as root"
exit
fi
# Copy executable to bin
cp ./target/release/crab /usr/bin/crab
chown root:root /bin/crab
chmod 6755 /bin/crab
# Set up config files
cp pam /etc/pam.d/crab
mkdir /usr/share/crab
cp conf /usr/share/crab/crab.conf
chmod 660 /usr/share/crab/crab.conf

View file

@ -3,20 +3,47 @@
# Installation
### From Source
First run `cargo build --release` to compile the binary.
Then run `install.sh` as root to install crab.
To be able to build the package, you need cargo wich you can get either though rust or rust up.
Run `uninstall.sh` as root to uninstall crab.
To build, run the following commands below in the root directory of the repo. Make sure to run the shell script as root.
```bash
cargo build --release
./deployments/source/install.sh
```
To uninstall, just run the following script as root.
```bash
./deployments/source/uninstall.sh
```
### Arch Based Systems
If you are on an arch based distro, crab is avaliable on the [AUR](https://aur.archlinux.org/packages/crab) as `crab`.
```
paru -S crab
```
# Configuration
Crab supports multiple users with persistence. Each line of the config is the username, then `true` of `false` if the crab authentication persists.
The default configuration file is stored in `/usr/share/crab/crab.conf` and must be coppied to `/etc/crab.conf`.
```bash
cp /usr/share/crab/crab.conf /etc/crab.conf
chown root:root /etc/crab.conf
chmod 600 /etc/crab.conf
```
Each line in the configuration specifies a different rule. Each rule is applied from top to bottom,
so the first onethat matches a user is what is used. The first word is either `permit` or `deny` to
allow or deny a certain group. Then the tags `persist` and `nopass` can be added to allow authoriziation
persistance or skipping respectively. Then a user can be specified by putting their name, or a group by a
colon then the groups name. Finally, if you dont want to run that user as root, you can add `as` and then
a user name to run the process as. All lines starting in a # will be ignored.
For Example
```
root true
tylerm false
deny :docker
permit nopass persist linus as root
#deny stallman
permit :wheel persist
permit nvidia as fu
```
The default configuration file is stored in `/usr/share/crab/crab.conf` and must be coppied to `/etc/crab.conf`.
Please make sure when editing your config that not normal users can edit the file, but only root. If normal users can edit the config, they can add themselvs as permitted and get elevated privilages.

264
src/auth.rs Normal file
View file

@ -0,0 +1,264 @@
use nix::unistd::{User, Group, Uid, Gid, self};
use crate::persist;
pub struct Config {
pub permit: bool,
pub persist: bool,
pub nopass: bool,
pub user_uid: Option<Uid>,
pub user_gid: Option<Gid>,
pub privlaged_uid: Uid,
}
/// Returns a option containing the config ad the specific pile path.
/// This function will print out config syntax errors to the standard
/// output if it failes to parse certain lines.
/// #### Arguments
/// * `path` - The path to the config file
/// #### Returns
/// * `None` - If the config failed to load
/// * `Some(Vec<Config>)` - If the config was sucessfully parsed.
pub fn load_config_file(path: &str) -> Option<Vec<Config>> {
let file = match std::fs::read_to_string(path) {
Err(e) => {
eprintln!("{}: {}", &path, e);
return None
},
Ok(data) => data
};
let mut configs = vec![];
for (line_num, line) in file.split("\n").enumerate() {
let args: Vec<&str> = line.split(" ").collect();
let len = args.len();
if line.starts_with("#") || line.trim() == "" {
continue;
}
if len < 2 {
config_error(line_num, "Not enough arguments");
continue;
}
let permit = match args[0] {
"permit" => true,
"deny" => false,
_ => {
config_error(line_num, "The first argument must be `permit` or `deny");
continue;
}
};
let (user_name, privlaged_name, name_index) = match args.iter().position(|&a| a == "as") {
Some(index) => {
if index != len - 2 {
config_error(line_num, "Target user not specified or to many arguments after `as`");
continue;
}
(args[index-1].to_string(), args[index+1].to_string(), index-1)
},
None => (args[len-1].to_string(), "root".to_string(), len-1)
};
let persist = args[1..name_index].contains(&"persist");
let nopass = args[1..name_index].contains(&"nopass");
for &check in args[1..name_index].iter() {
match check {
"persist" => continue,
"nopass" => continue,
_ => {
config_error(line_num, &format!("Unexpected token `{}`", check))
}
}
}
let (user_uid, user_gid) =
if user_name.starts_with(":") {
match get_gid_from_name(&user_name[1..]) {
Some(gid) => (None, Some(gid)),
None => {
config_error(line_num, &format!("Group `{}` does not exist", &user_name[1..]));
continue;
}
}
} else {
match get_uid_from_name(&user_name) {
Some(uid) => (Some(uid), None),
None => {
config_error(line_num, &format!("User `{}` does not exist", user_name));
continue;
}
}
};
let privlaged_uid = match get_uid_from_name(&privlaged_name) {
Some(uid) => uid,
None => {
config_error(line_num, &format!("User `{}` does not exist", privlaged_name));
continue;
}
};
configs.push(Config {
permit,
persist,
nopass,
user_uid,
user_gid,
privlaged_uid
});
}
Some(configs)
}
/// Print a crab config error to the standard output
fn config_error(line_num: usize, message: &str) {
eprintln!("Error in config at line {}: {}", line_num, message);
}
/// Returns a Uid from a Users name
/// #### Arguments
/// * `name` - The name of the user
/// #### Returns
/// * `None` - If the user doesn't exist
/// * `Some(Gid)` - If the user exists
fn get_uid_from_name(name: &str) -> Option<Uid> {
return match User::from_name(name) {
Ok(result) => match result {
Some(data) => Some(data.uid),
None => None
},
Err(_) => None
}
}
/// Returns a Uesrs name from a Uid
/// #### Arguments
/// * `uid` - The uid of the user
/// #### Returns
/// * `None` - If the user doesn't exist
/// * `Some(Gid)` - If the user exists
fn get_name_from_uid(uid: Uid) -> Option<String> {
return match User::from_uid(uid) {
Ok(result) => match result {
Some(data) => Some(data.name),
None => None
},
Err(_) => None
}
}
/// Returns a Gid from a Groups name
/// #### Arguments
/// * `name` - The name of the group
/// #### Returns
/// * `None` - If the group doesn't exist
/// * `Some(Gid)` - If the group exists
fn get_gid_from_name(name: &str) -> Option<Gid> {
return match Group::from_name(name) {
Ok(result) => match result {
Some(data) => Some(data.gid),
None => None
},
Err(_) => None
}
}
/// Returns a vector of group names of the groups the current effective uid is in
/// #### Returns
/// A vector of strings of the groups the user is in. If the vector is empty,
/// either the function coudn't retrieve the users groups, or the user is not in
/// any groups.
fn get_groups() -> Vec<Gid> {
let groups = match unistd::getgroups() {
Ok(data) => data,
Err(_) => return vec![]
};
groups
}
/// Returns if the user is authorized given a sepcific config and user name.
/// #### Arguments
/// * `config` - A config struct contaning the authorization settings for crab
/// * `uid` - The uid to check is authorized
/// #### Returns
/// * `None` - If the uid is not authorized
/// * `Some(Config)` - If the uid is authorized, returns the specific index of the associated config
pub fn authorize(configs: &Vec<Config>, uid: Uid) -> Option<usize> {
let groups = get_groups();
for (config_index, config) in configs.iter().enumerate() {
if config.user_gid.is_some() {
if groups.contains(&config.user_gid.unwrap()) {
if config.permit {
return Some(config_index)
} else {
return None
}
}
} else if config.user_uid.is_some() {
if config.user_uid.unwrap() == uid {
if config.permit {
return Some(config_index)
} else {
return None
}
}
}
}
None
}
/// Authenticates an authorized user for the current session.
/// If the user is already persisted, it will attempt to
/// read the persist file, and then skip authentication if
/// the is still persisted.
/// #### Arguments
/// * `config` - The config associated with the user to authenticate with
/// * `force_pass` - Force a password prompt even if persistance is set to true
/// * `uid` - The user uid that is authenticating
/// #### Returns
/// * `true` - If the user authenticated sucessfully
/// * `false` - If the user failed to authenticate
pub fn authenticate(config: &Config, force_pass: bool, uid: Uid) -> bool {
let name = match get_name_from_uid(uid) {
Some(data) => data,
None => return false
};
if config.nopass || ( !force_pass && config.persist && persist::get_persist(&name) ) {
return true;
}
let input = match rpassword::prompt_password(format!("crab ({}) password: ", &name)) {
Ok(data) => data,
Err(_) => return false
};
let mut auth = match pam::Authenticator::with_password("crab") {
Ok(data) => data,
Err(_) => return false
};
auth.get_handler().set_credentials(&name, input);
if !auth.authenticate().is_ok() || !auth.open_session().is_ok() {
return false;
}
if !force_pass && config.persist {
persist::set_persist(&name);
} else if force_pass {
persist::remove_persist(&name);
}
return true;
}

View file

@ -1,66 +1,92 @@
pub struct Flags {
pub help: bool,
pub version: bool,
pub dont_persist: bool,
pub arg_count: usize
}
pub fn parse(args: &[String]) -> Option<Flags> {
let mut flags = Flags {
help: false,
version: false,
dont_persist: false,
arg_count: 0
};
for arg in args {
if !is_arg(&arg) { break; }
flags.arg_count += 1;
if arg.starts_with("--") {
let flag = &arg[2..];
if !set_flag(&flag, &mut flags) {
eprintln!("Invalid argument: {}", arg);
return None
}
} else {
let flag = &arg[1..];
for char in flag.chars() {
if !set_flag(&char.to_string(), &mut flags) {
eprintln!("Invalid argument: {}", arg);
return None
}
}
}
}
Some(flags)
}
fn is_arg(arg: &str) -> bool {
return arg.starts_with("-");
}
const HELP_FLAG: &str = "help h";
const VERSION_FLAG: &str = "version v";
const DONT_PERSIST: &str = "d";
fn set_flag(arg: &str, flags: &mut Flags) -> bool {
if has_flag_set(&arg, HELP_FLAG) {
flags.help = true;
return true
} else if has_flag_set(&arg, VERSION_FLAG) {
flags.version = true;
return true
} else if has_flag_set(&arg, DONT_PERSIST) {
flags.dont_persist = true;
return true
}
false
pub struct Flags {
pub help: bool,
pub version: bool,
pub dont_persist: bool,
pub arg_count: usize
}
fn has_flag_set(arg: &str, check: &str) -> bool {
for check_arg in check.split(" ") {
if check_arg == arg {
return true
/// Parses an list of String arguments into a set of flags that can be used by crab
/// #### Arguments
/// * `args` - The list of String arguments
/// #### Returns
/// * `None` - If there is an invalid argument in the list
/// * `Some(Flags)` - If the arguments were secussfully parsed, returning the flags
pub fn parse(args: &[String]) -> Option<Flags> {
let mut flags = Flags {
help: false,
version: false,
dont_persist: false,
arg_count: 0
};
for arg in args {
if !is_arg(&arg) { break; }
flags.arg_count += 1;
if arg.starts_with("--") {
let flag = &arg[2..];
if !set_flag(&flag, &mut flags) {
eprintln!("Invalid argument: {}", arg);
return None
}
} else {
let flag = &arg[1..];
for char in flag.chars() {
if !set_flag(&char.to_string(), &mut flags) {
eprintln!("Invalid argument: {}", arg);
return None
}
}
}
}
}
return false
Some(flags)
}
/// Checks if a given string is a given argument
fn is_arg(arg: &str) -> bool {
return arg.starts_with("-");
}
/// Sets a flag in a `Flags` struct
/// #### Arguments
/// * `arg` - The argument to check
/// * `flags` - The `Flags` stuct to update
/// #### Returns
/// * `true` - If the argument passed is a valid flag
/// * `false` - If the argument passed is not a valid flag
fn set_flag(arg: &str, flags: &mut Flags) -> bool {
if has_flag_set(&arg, HELP_FLAG) {
flags.help = true;
return true
} else if has_flag_set(&arg, VERSION_FLAG) {
flags.version = true;
return true
} else if has_flag_set(&arg, DONT_PERSIST) {
flags.dont_persist = true;
return true
}
false
}
/// Returns if a given argument is a certain flag
/// #### Arguments
/// * `arg` - The arch to check
/// #### Returns
/// * `true` - If the argument matches the flag
/// * `false` - If the argument doesn't match the flag
fn has_flag_set(arg: &str, check: &str) -> bool {
for check_arg in check.split(" ") {
if check_arg == arg {
return true
}
}
return false
}

View file

@ -1,10 +0,0 @@
pub fn help() {
let help =
"Usage:
crab [-d] command [args]
Options:
-v --version Get the current version of the package
-h --help Generates the crab help message
-d If your user is set to persist, dont save persistance";
println!("{}", help);
}

View file

@ -1,142 +1,100 @@
use std::env;
use std::process::ExitCode;
use pwd::Passwd;
use nix::unistd;
extern crate time;
const ERROR_ARGS: u8 = 1;
const ERROR_CONFIG: u8 = 2;
const ERROR_NO_USER: u8 = 3;
const ERROR_NOT_AUTHORIZED: u8 = 4;
const ERROR_AUTH_FAILED: u8 = 5;
const ERROR_RUN_ROOT: u8 = 6;
mod persist;
mod auth;
mod flags;
mod help;
mod persist;
mod secure;
const ERROR_ARGS: u8 = 1;
const ERROR_CONFIG: u8 = 2;
const ERROR_NOT_AUTHORIZED: u8 = 3;
const ERROR_AUTH_FAILED: u8 = 4;
const ERROR_ELEVATE_PRIVILEGES: u8 = 5;
fn main() -> ExitCode {
// gets the arguments from the env
let args: Vec<String> = env::args().collect();
// pase the arguments into valid flags that are usuable by crab
let flags = match flags::parse(&args[1..]) {
Some(data) => data,
// if there is an invalid flag, print the help message and exit
None => {
help::help();
help();
return ExitCode::from(ERROR_ARGS);
}
};
// If the version arg flag is set, print the crab version
if flags.version {
println!("crab version 0.0.5");
println!("crab version 0.0.6");
return ExitCode::SUCCESS;
}
// If the help arg flag is set, print the crab help message
if flags.help {
help::help();
help();
return ExitCode::SUCCESS;
}
// If the amount of acutal command arguments is less than two, a.k.a just `crab`, print the command usage
if args.len() - flags.arg_count < 2 {
println!("usage: crab [-d] command [args]");
return ExitCode::SUCCESS;
}
let config = match config("/etc/crab.conf") {
// Load the command config from /etc
let configs = match auth::load_config_file("/etc/crab.conf") {
Some(data) => data,
None => return ExitCode::from(ERROR_CONFIG)
};
let user = match Passwd::current_user() {
// check if the user is authorized
let auth = match auth::authorize(&configs, nix::unistd::getuid()) {
Some(data) => data,
None => {
eprintln!("You dont exist.");
return ExitCode::from(ERROR_NO_USER);
}
};
let persist = match allowed(&config, &user.name) {
Some(data) => data && !flags.dont_persist,
None => {
eprintln!("Operation Not Permitted.");
return ExitCode::from(ERROR_NOT_AUTHORIZED);
}
};
if !validate(&user.name, persist) {
// authenticate the user
if !auth::authenticate(&configs[auth], flags.dont_persist, nix::unistd::getuid()) {
eprintln!("Authentication failed.");
return ExitCode::from(ERROR_AUTH_FAILED);
}
if !unistd::setuid(unistd::geteuid()).is_ok() || !unistd::setgid(unistd::getegid()).is_ok() {
eprintln!("Failed to set root permissions");
return ExitCode::from(ERROR_RUN_ROOT);
// elevate privileges
if nix::unistd::setuid(configs[auth].privlaged_uid).is_err() {
eprintln!("Failed to elevate privileges.");
return ExitCode::from(ERROR_ELEVATE_PRIVILEGES);
};
// execute the passed command
let start = 1 + flags.arg_count;
let err = exec::execvp(&args[start], &args[start..]);
// print an error if an error was returned
eprintln!("{}", err);
ExitCode::SUCCESS
}
struct Config {
users: Vec<(String, bool)>
}
fn validate(user: &str, persist: bool) -> bool {
if persist && persist::get_persist(user) {
return true;
}
let input = match rpassword::prompt_password(format!("crab ({}) password: ", user)) {
Ok(data) => data,
Err(_) => return false
};
let mut auth = match pam::Authenticator::with_password("crab") {
Ok(data) => data,
Err(_) => return false
};
auth.get_handler().set_credentials(user.to_owned(), input);
if !auth.authenticate().is_ok() || !auth.open_session().is_ok() {
return false;
}
if persist {
persist::set_persist(user);
}
return true;
}
fn allowed(config: &Config, user: &str) -> Option<bool> {
for (name, persist) in &config.users {
if name == user {
return Some(persist.clone());
}
}
None
}
fn config(path: &str) -> Option<Config> {
let file = match std::fs::read_to_string(path) {
Err(e) => {
eprintln!("{}: {}", &path, e);
return None
},
Ok(data) => data
};
let mut users = vec![];
for (line_num, line) in file.split("\n").enumerate() {
let args: Vec<&str> = line.split(" ").collect();
if line.trim() == "" {
continue;
}
if args.len() < 2 {
eprintln!("Error in config at line {}: Not enough arguments", line_num);
continue;
}
let user: String = args[0].to_string();
let persist: bool = match args[1].parse() {
Err(e) => {
eprintln!("Error in config at line {}: {}", line_num, e);
continue;
},
Ok(data) => data
};
users.push((user, persist));
}
Some(Config{users})
/// Prints the help message to the standard output
fn help() {
let help =
"Usage:
crab [-d] command [args]
Options:
-v --version Get the current version of the package
-h --help Generates the crab help message
-d If your user is set to persist, dont save persistance";
println!("{}", help);
}

View file

@ -1,69 +1,116 @@
use std::time::SystemTime;
use serde_json::Value;
use crate::secure;
const PERSIST_TIME: u64 = 60 * 3;
const PERSIST_PATH: &str = "/var/run/crab";
/// Returns true or false if a user is currently persisted from
/// a prior authentication. If the persist file had been tampered
/// with, or not trusted, it will be ignored, and return false.
/// #### Arguments
/// * `user` - The user to check if is persisted
/// #### Returns
/// * `true` - If the user is persisted
/// * `false` - If the user is not persisted, or if the persist file is not trusted
pub fn get_persist(user: &str) -> bool {
let json = match get_terminal_config() {
Some(data) => data,
None => return false
};
let timestamp = match json[user].as_u64() {
Some(data) => data,
None => return false
};
return now() - timestamp < PERSIST_TIME && timestamp - 1 < now();
let json = match get_persist_config() {
Some(data) => data,
None => return false
};
let timestamp = match json[user].as_u64() {
Some(data) => data,
None => return false
};
return now() - timestamp < PERSIST_TIME && timestamp - 1 < now();
}
/// Updates a user in the current sessions persist file
/// #### Arguments
/// * `user` - The user to set persisted
pub fn set_persist(user: &str) {
let mut json = match get_terminal_config() {
Some(data) => data,
None => return
};
json[user] = Value::from(now());
let id = match get_terminal_process() {
Some(data) => data,
None => return
};
match secure::write_file(PERSIST_PATH, &format!("{}", id), &json.to_string()) {
Ok(_) => {},
Err(e) => {
eprintln!("Internal Error: {}", e)
}
};
let mut json = match get_persist_config() {
Some(data) => data,
None => return
};
json[user] = Value::from(now());
let session = match get_current_session() {
Some(data) => data,
None => return
};
match secure::write_file(PERSIST_PATH, &format!("{}", session), &json.to_string()) {
Ok(_) => {},
Err(_) => {
eprintln!("crab: An internal error has occured");
}
};
}
fn get_terminal_process() -> Option<i32> {
let id: i32 = match std::process::id().try_into() {
Ok(data) => data,
Err(_) => return None
};
let stat = match procinfo::pid::stat(id) {
Ok(data) => data,
Err(_) => return None
};
Some(stat.session)
/// Removes a user from the current sessions persist file
/// #### Arguments
/// * `user` - The user to set non-persisted
pub fn remove_persist(user: &str) {
let mut json = match get_persist_config() {
Some(data) => data,
None => return
};
json[user] = Value::from(0);
let session = match get_current_session() {
Some(data) => data,
None => return
};
match secure::write_file(PERSIST_PATH, &format!("{}", session), &json.to_string()) {
Ok(_) => {},
Err(_) => {
eprintln!("crab: An internal error has occured");
}
};
}
fn get_terminal_config() -> Option<Value> {
let id = match get_terminal_process() {
Some(data) => data,
None => return None
};
let data = match secure::read_file(PERSIST_PATH, &format!("{}", id)) {
Some(data) => data,
None => "{}".to_string()
};
let json: Value = match serde_json::from_str(&data) {
Ok(data) => data,
Err(_) => return None
};
Some(json)
/// Gets the current session that crab is running in
/// #### Returns
/// * `None` - If crab failed to get the current session
/// * `Some(i32)` - If the session is retrieved, returns the i32/pid_t session id
fn get_current_session() -> Option<i32> {
let pid: i32 = match std::process::id().try_into() {
Ok(data) => data,
Err(_) => return None
};
let pid_stat = match procinfo::pid::stat(pid) {
Ok(data) => data,
Err(_) => return None
};
Some(pid_stat.session)
}
/// Gets the current persist file for the current session
/// #### Returns
/// * `None` - If the persist file is untrusted or doesnt exist
/// * `Some(Value)` - If the persist file is retrieved, returns the serde_json Value of the file
fn get_persist_config() -> Option<Value> {
let session = match get_current_session() {
Some(data) => data,
None => return None
};
let data = match secure::read_file(PERSIST_PATH, &format!("{}", session)) {
Some(data) => data,
None => "{}".to_string()
};
let json: Value = match serde_json::from_str(&data) {
Ok(data) => data,
Err(_) => return None
};
Some(json)
}
// Gets the current time in seconds since the Unix Epoch
fn now() -> u64 {
return SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
return SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
}

View file

@ -1,51 +1,84 @@
use std::{os::{unix::prelude::PermissionsExt, linux::fs::MetadataExt}, fs, io::ErrorKind};
use nix::unistd;
use std::{os::{unix::prelude::PermissionsExt, linux::fs::MetadataExt}, fs, io::{self, ErrorKind}, path::Path};
use nix::unistd::{self, Uid, Gid};
pub fn write_file(dir: &str, file: &str, data: &str) -> Result<(), Box<dyn std::error::Error>> {
std::fs::create_dir_all(dir)?;
make_file_root(dir)?;
let path = path(dir, file);
std::fs::write(&path, "")?;
make_file_root(&path)?;
std::fs::write(&path, data)?;
Ok(())
/// Writes a file securly to a specified path with given data
/// #### Arguments
/// * `dir` - The directory of the secure folder
/// * `file` - The file name to write
/// * `data` - The data to write
/// #### Returns
/// A ``io::Result<()>`` if the write succeded or failed
pub fn write_file(dir: &str, file: &str, data: &str) -> Result<(), io::Error> {
fs::create_dir_all(dir)?;
set_file_permissions(0, 0, 0o100600, dir)?;
let path = path(dir, file);
fs::write(&path, data)?;
set_file_permissions(0, 0, 0o100600, &path)?;
Ok(())
}
/// Reads the file secutly to a specified path. If the file has
/// been tampered (permissions have been changed), it ignores the
/// file.
/// #### Arguments
/// * `dir` - The directory of the secure folder
/// * `file` - The file name to write
/// #### Returns
/// * `None` - If the files doesnt exist or isnt trusted
/// * `Some(String) - If the file is trusted, it returns the file's contents
pub fn read_file(dir: &str, file: &str) -> Option<String> {
let path = path(dir,file);
if !is_file_root(&path) {
return None;
}
match std::fs::read_to_string(&path) {
Ok(data) => return Some(data),
Err(_) => return None
};
}
fn make_file_root(path: &str) -> Result<(), Box<dyn std::error::Error>> {
unistd::chown(std::path::Path::new(path), Some(unistd::Uid::from(0)), Some(unistd::Gid::from(0)))?;
let metadata = std::fs::metadata(path)?;
let mut perms = metadata.permissions();
perms.set_mode(0o100600);
fs::set_permissions(path, perms)?;
Ok(())
}
fn is_file_root(path: &str) -> bool {
return check_file_permissions(0, 0, 0o100600, path);
}
fn check_file_permissions(uid: u32, gid: u32, mode: u32, path: &str) -> bool {
let metadata = match std::fs::metadata(path) {
Ok(data) => data,
Err(e) => {
return e.kind() == ErrorKind::NotFound
let path = path(dir,file);
if !check_file_permissions(0, 0, 0o100600, &path) {
return None;
}
};
let perms = metadata.permissions();
return perms.mode() == mode && metadata.st_uid() == uid && metadata.st_gid() == gid;
match fs::read_to_string(&path) {
Ok(data) => return Some(data),
Err(_) => return None
};
}
/// Sets the permission for a secure file
/// #### Arguments
/// * `uid` - The user to own the file
/// * `gid` - The group to own the file
/// * `mode` - The mode permissions of the file
/// * `path` - The path of the secure file
/// #### Returns
/// A ``io::Result<()>`` if the write succeded or failed
fn set_file_permissions(uid: u32, gid: u32, mode: u32, path: &str) -> Result<(), io::Error> {
unistd::chown(Path::new(path), Some(Uid::from(uid)), Some(Gid::from(gid)))?;
let metadata = fs::metadata(path)?;
let mut perms = metadata.permissions();
perms.set_mode(mode);
fs::set_permissions(path, perms)?;
Ok(())
}
/// Checks if the files permissions equals the given parameters
/// #### Arguments
/// * `uid` - The user to own the file
/// * `gid` - The group to own the file
/// * `mode` - The mode permissions of the file
/// * `path` - The path of the secure file
/// #### Returns
/// True or false if the files permissions match
fn check_file_permissions(uid: u32, gid: u32, mode: u32, path: &str) -> bool {
let metadata = match fs::metadata(path) {
Ok(data) => data,
Err(e) => {
return e.kind() == ErrorKind::NotFound;
}
};
let perms = metadata.permissions();
return perms.mode() == mode && metadata.st_uid() == uid && metadata.st_gid() == gid;
}
/// Get the path of a file given a directory and file name
fn path(dir: &str, file: &str) -> String {
return format!("{}/{}", dir, file);
}
return format!("{}/{}.persist", dir, file);
}

View file

@ -1,16 +0,0 @@
#!/bin/bash
if [[ $(/usr/bin/id -u) -ne 0 ]]; then
echo "Please run this as root"
exit
fi
# Delete crab files
rm /usr/bin/crab
chown root:root /bin/crab
chmod 6755 /bin/crab
# Set up config files
cp pam /etc/pam.d/crab
cp -n conf /etc/crab.conf
chmod 660 /etc/crab.conf