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 /target
Cargo.lock Cargo.lock
test.sh test.sh
crab.tar.gz

68
Cargo.lock generated
View file

@ -34,13 +34,12 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "crab" name = "crab"
version = "0.0.5" version = "0.0.6"
dependencies = [ dependencies = [
"exec", "exec",
"nix", "nix",
"pam", "pam",
"procinfo", "procinfo",
"pwd",
"rpassword", "rpassword",
"serde_json", "serde_json",
"time", "time",
@ -144,15 +143,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 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]] [[package]]
name = "procinfo" name = "procinfo"
version = "0.4.2" version = "0.4.2"
@ -165,25 +155,6 @@ dependencies = [
"rustc_version", "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]] [[package]]
name = "rpassword" name = "rpassword"
version = "7.1.0" version = "7.1.0"
@ -241,37 +212,6 @@ dependencies = [
"serde", "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]] [[package]]
name = "time" name = "time"
version = "0.3.17" version = "0.3.17"
@ -288,12 +228,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
[[package]]
name = "unicode-ident"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
[[package]] [[package]]
name = "users" name = "users"
version = "0.8.1" version = "0.8.1"

View file

@ -1,11 +1,10 @@
[package] [package]
name = "crab" name = "crab"
version = "0.0.5" version = "0.0.6"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
pam = "0.7.0" pam = "0.7.0"
pwd = "1.4.0"
nix = "0.25.0" nix = "0.25.0"
exec = "0.3.1" exec = "0.3.1"
rpassword = "7.1.0" 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 pkgbase = crab
pkgdesc = A rusty permission authentication system pkgdesc = A rusty permission authentication system
pkgver = 0.0.5 pkgver = 0.0.6
pkgrel = 2 pkgrel = 2
url = https://g.tylerm.dev/tylermurphy534/crab.git url = https://g.tylerm.dev/tylermurphy534/crab
arch = x86_64 arch = x86_64
arch = i686 arch = i686
license = GPL3 license = GPL3
makedepends = git
makedepends = cargo makedepends = cargo
depends = glibc depends = glibc
depends = pam 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 md5sums = SKIP
pkgname = crab pkgname = crab

View file

@ -1,24 +1,24 @@
# Maintainer: Tyler Murphy <tylermurphy534@gmail.com> # Maintainer: Tyler Murphy <tylermurphy534@gmail.com>
pkgname=crab pkgname=crab
pkgver=0.0.5 pkgver=0.0.6
pkgrel=2 pkgrel=1
pkgdesc="A rusty permission authentication system" pkgdesc="A rusty permission authentication system"
arch=('x86_64' 'i686') arch=('x86_64' 'i686')
url="https://g.tylerm.dev/tylermurphy534/crab.git" url="https://g.tylerm.dev/tylermurphy534/crab"
license=('GPL3') license=('GPL3')
makedepends=('git' 'cargo') makedepends=('cargo')
depends=('glibc' 'pam') depends=('glibc' 'pam')
source=("git+$url") source=("$pkgname-$pkgver.tar.gz::https://f.tylerm.dev/source/$pkgname/$pkgname-$pkgver.tar.gz")
md5sums=('SKIP') md5sums=('SKIP')
build() { build() {
cd crab cd "$srcdir"
cargo build --release cargo build --release
} }
package() { package() {
cd crab cd "$srcdir"
install -D --mode=6755 --owner=root --group=root ./target/release/crab ${pkgdir}/usr/bin/crab install -D --mode=4755 --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=600 --owner=root --group=root ./config/pam ${pkgdir}/etc/pam.d/crab
install -D --mode=660 --owner=root --group=root conf ${pkgdir}/usr/share/crab/crab.conf 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 # Installation
### From Source ### From Source
First run `cargo build --release` to compile the binary. To be able to build the package, you need cargo wich you can get either though rust or rust up.
Then run `install.sh` as root to install crab.
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 ### 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`. 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 # 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 For Example
``` ```
root true deny :docker
tylerm false 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,3 +1,9 @@
const HELP_FLAG: &str = "help h";
const VERSION_FLAG: &str = "version v";
const DONT_PERSIST: &str = "d";
pub struct Flags { pub struct Flags {
pub help: bool, pub help: bool,
pub version: bool, pub version: bool,
@ -5,6 +11,13 @@ pub struct Flags {
pub arg_count: usize pub arg_count: usize
} }
/// 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> { pub fn parse(args: &[String]) -> Option<Flags> {
let mut flags = Flags { let mut flags = Flags {
help: false, help: false,
@ -34,14 +47,20 @@ pub fn parse(args: &[String]) -> Option<Flags> {
Some(flags) Some(flags)
} }
/// Checks if a given string is a given argument
fn is_arg(arg: &str) -> bool { fn is_arg(arg: &str) -> bool {
return arg.starts_with("-"); return arg.starts_with("-");
} }
const HELP_FLAG: &str = "help h";
const VERSION_FLAG: &str = "version v";
const DONT_PERSIST: &str = "d";
/// 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 { fn set_flag(arg: &str, flags: &mut Flags) -> bool {
if has_flag_set(&arg, HELP_FLAG) { if has_flag_set(&arg, HELP_FLAG) {
flags.help = true; flags.help = true;
@ -56,6 +75,13 @@ fn set_flag(arg: &str, flags: &mut Flags) -> bool {
false 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 { fn has_flag_set(arg: &str, check: &str) -> bool {
for check_arg in check.split(" ") { for check_arg in check.split(" ") {
if check_arg == arg { if check_arg == arg {

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::env;
use std::process::ExitCode; use std::process::ExitCode;
use pwd::Passwd;
use nix::unistd;
extern crate time;
mod auth;
mod flags;
mod persist;
mod secure;
const ERROR_ARGS: u8 = 1; const ERROR_ARGS: u8 = 1;
const ERROR_CONFIG: u8 = 2; const ERROR_CONFIG: u8 = 2;
const ERROR_NO_USER: u8 = 3; const ERROR_NOT_AUTHORIZED: u8 = 3;
const ERROR_NOT_AUTHORIZED: u8 = 4; const ERROR_AUTH_FAILED: u8 = 4;
const ERROR_AUTH_FAILED: u8 = 5; const ERROR_ELEVATE_PRIVILEGES: u8 = 5;
const ERROR_RUN_ROOT: u8 = 6;
mod persist;
mod flags;
mod help;
mod secure;
fn main() -> ExitCode { fn main() -> ExitCode {
// gets the arguments from the env
let args: Vec<String> = env::args().collect(); 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..]) { let flags = match flags::parse(&args[1..]) {
Some(data) => data, Some(data) => data,
// if there is an invalid flag, print the help message and exit
None => { None => {
help::help(); help();
return ExitCode::from(ERROR_ARGS); return ExitCode::from(ERROR_ARGS);
} }
}; };
// If the version arg flag is set, print the crab version
if flags.version { if flags.version {
println!("crab version 0.0.5"); println!("crab version 0.0.6");
return ExitCode::SUCCESS; return ExitCode::SUCCESS;
} }
// If the help arg flag is set, print the crab help message
if flags.help { if flags.help {
help::help(); help();
return ExitCode::SUCCESS; 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 { if args.len() - flags.arg_count < 2 {
println!("usage: crab [-d] command [args]"); println!("usage: crab [-d] command [args]");
return ExitCode::SUCCESS; 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, Some(data) => data,
None => return ExitCode::from(ERROR_CONFIG) 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, 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 => { None => {
eprintln!("Operation Not Permitted."); eprintln!("Operation Not Permitted.");
return ExitCode::from(ERROR_NOT_AUTHORIZED); 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."); eprintln!("Authentication failed.");
return ExitCode::from(ERROR_AUTH_FAILED); return ExitCode::from(ERROR_AUTH_FAILED);
} }
if !unistd::setuid(unistd::geteuid()).is_ok() || !unistd::setgid(unistd::getegid()).is_ok() { // elevate privileges
eprintln!("Failed to set root permissions"); if nix::unistd::setuid(configs[auth].privlaged_uid).is_err() {
return ExitCode::from(ERROR_RUN_ROOT); eprintln!("Failed to elevate privileges.");
return ExitCode::from(ERROR_ELEVATE_PRIVILEGES);
}; };
// execute the passed command
let start = 1 + flags.arg_count; let start = 1 + flags.arg_count;
let err = exec::execvp(&args[start], &args[start..]); let err = exec::execvp(&args[start], &args[start..]);
// print an error if an error was returned
eprintln!("{}", err); eprintln!("{}", err);
ExitCode::SUCCESS ExitCode::SUCCESS
} }
struct Config {
users: Vec<(String, bool)>
}
fn validate(user: &str, persist: bool) -> bool { /// Prints the help message to the standard output
if persist && persist::get_persist(user) { fn help() {
return true; let help =
} "Usage:
let input = match rpassword::prompt_password(format!("crab ({}) password: ", user)) { crab [-d] command [args]
Ok(data) => data, Options:
Err(_) => return false -v --version Get the current version of the package
}; -h --help Generates the crab help message
let mut auth = match pam::Authenticator::with_password("crab") { -d If your user is set to persist, dont save persistance";
Ok(data) => data, println!("{}", help);
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})
} }

View file

@ -1,13 +1,22 @@
use std::time::SystemTime; use std::time::SystemTime;
use serde_json::Value; use serde_json::Value;
use crate::secure; use crate::secure;
const PERSIST_TIME: u64 = 60 * 3; const PERSIST_TIME: u64 = 60 * 3;
const PERSIST_PATH: &str = "/var/run/crab"; 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 { pub fn get_persist(user: &str) -> bool {
let json = match get_terminal_config() { let json = match get_persist_config() {
Some(data) => data, Some(data) => data,
None => return false None => return false
}; };
@ -18,42 +27,78 @@ pub fn get_persist(user: &str) -> bool {
return now() - timestamp < PERSIST_TIME && timestamp - 1 < now(); 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) { pub fn set_persist(user: &str) {
let mut json = match get_terminal_config() { let mut json = match get_persist_config() {
Some(data) => data, Some(data) => data,
None => return None => return
}; };
json[user] = Value::from(now()); json[user] = Value::from(now());
let id = match get_terminal_process() { let session = match get_current_session() {
Some(data) => data, Some(data) => data,
None => return None => return
}; };
match secure::write_file(PERSIST_PATH, &format!("{}", id), &json.to_string()) { match secure::write_file(PERSIST_PATH, &format!("{}", session), &json.to_string()) {
Ok(_) => {}, Ok(_) => {},
Err(e) => { Err(_) => {
eprintln!("Internal Error: {}", e) eprintln!("crab: An internal error has occured");
} }
}; };
} }
fn get_terminal_process() -> Option<i32> {
let id: i32 = match std::process::id().try_into() { /// Removes a user from the current sessions persist file
Ok(data) => data, /// #### Arguments
Err(_) => return None /// * `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
}; };
let stat = match procinfo::pid::stat(id) { json[user] = Value::from(0);
Ok(data) => data, let session = match get_current_session() {
Err(_) => return None 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");
}
}; };
Some(stat.session)
} }
fn get_terminal_config() -> Option<Value> {
let id = match get_terminal_process() { /// 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, Some(data) => data,
None => return None None => return None
}; };
let data = match secure::read_file(PERSIST_PATH, &format!("{}", id)) { let data = match secure::read_file(PERSIST_PATH, &format!("{}", session)) {
Some(data) => data, Some(data) => data,
None => "{}".to_string() None => "{}".to_string()
}; };
@ -64,6 +109,8 @@ fn get_terminal_config() -> Option<Value> {
Some(json) Some(json)
} }
// Gets the current time in seconds since the Unix Epoch
fn now() -> u64 { 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 std::{os::{unix::prelude::PermissionsExt, linux::fs::MetadataExt}, fs, io::{self, ErrorKind}, path::Path};
use nix::unistd; 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)?; /// Writes a file securly to a specified path with given data
make_file_root(dir)?; /// #### 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); let path = path(dir, file);
std::fs::write(&path, "")?; fs::write(&path, data)?;
make_file_root(&path)?; set_file_permissions(0, 0, 0o100600, &path)?;
std::fs::write(&path, data)?;
Ok(()) 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> { pub fn read_file(dir: &str, file: &str) -> Option<String> {
let path = path(dir,file); let path = path(dir,file);
if !is_file_root(&path) { if !check_file_permissions(0, 0, 0o100600, &path) {
return None; return None;
} }
match std::fs::read_to_string(&path) { match fs::read_to_string(&path) {
Ok(data) => return Some(data), Ok(data) => return Some(data),
Err(_) => return None 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)))?; /// Sets the permission for a secure file
let metadata = std::fs::metadata(path)?; /// #### 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(); let mut perms = metadata.permissions();
perms.set_mode(0o100600); perms.set_mode(mode);
fs::set_permissions(path, perms)?; fs::set_permissions(path, perms)?;
Ok(()) Ok(())
} }
fn is_file_root(path: &str) -> bool {
return check_file_permissions(0, 0, 0o100600, path);
}
/// 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 { fn check_file_permissions(uid: u32, gid: u32, mode: u32, path: &str) -> bool {
let metadata = match std::fs::metadata(path) { let metadata = match fs::metadata(path) {
Ok(data) => data, Ok(data) => data,
Err(e) => { Err(e) => {
return e.kind() == ErrorKind::NotFound return e.kind() == ErrorKind::NotFound;
} }
}; };
let perms = metadata.permissions(); let perms = metadata.permissions();
return perms.mode() == mode && metadata.st_uid() == uid && metadata.st_gid() == gid; 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 { 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