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:
commit
ce6af95b74
19 changed files with 657 additions and 376 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
/target
|
||||
Cargo.lock
|
||||
test.sh
|
||||
crab.tar.gz
|
||||
|
|
68
Cargo.lock
generated
68
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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
1
conf
|
@ -1 +0,0 @@
|
|||
root true
|
5
config/default
Normal file
5
config/default
Normal file
|
@ -0,0 +1,5 @@
|
|||
deny :docker
|
||||
permit nopass persist linus as root
|
||||
#deny stallman
|
||||
permit :wheel persist
|
||||
permit nvidia as fu
|
|
@ -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
|
||||
|
|
|
@ -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
21
deployments/source/install.sh
Executable 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
11
deployments/source/uninstall.sh
Executable 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
|
17
install.sh
17
install.sh
|
@ -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
|
41
readme.md
41
readme.md
|
@ -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
264
src/auth.rs
Normal 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;
|
||||
}
|
140
src/flags.rs
140
src/flags.rs
|
@ -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
|
||||
}
|
||||
|
|
10
src/help.rs
10
src/help.rs
|
@ -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);
|
||||
}
|
142
src/main.rs
142
src/main.rs
|
@ -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);
|
||||
}
|
||||
|
|
147
src/persist.rs
147
src/persist.rs
|
@ -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();
|
||||
}
|
||||
|
|
119
src/secure.rs
119
src/secure.rs
|
@ -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);
|
||||
}
|
||||
|
|
16
uninstall.sh
16
uninstall.sh
|
@ -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
|
Loading…
Reference in a new issue