#![forbid(unsafe_code)]
#![deny(missing_docs)]
#![allow(clippy::redundant_field_names)]
use log::debug;
use users::{
Users,
UsersCache,
};
mod cli;
mod errors;
mod exporter;
mod file;
mod httpd;
mod rctlstate;
use errors::ExporterError;
use exporter::Exporter;
use file::FileExporter;
use rctlstate::RctlState;
#[cfg(feature = "bcrypt_cmd")]
use dialoguer::Password;
#[cfg(feature = "auth")]
use httpd::auth::BasicAuthConfig;
#[cfg(feature = "bcrypt_cmd")]
use rand::{
distributions::Alphanumeric,
thread_rng,
Rng,
};
#[cfg(feature = "rc_script")]
const RC_SCRIPT: &str = include_str!("../rc.d/jail_exporter.in");
fn is_racct_rctl_available() -> Result<(), ExporterError> {
debug!("Checking RACCT/RCTL status");
match RctlState::check() {
RctlState::Disabled => {
Err(ExporterError::RctlUnavailable(
"Present, but disabled; enable using \
kern.racct.enable=1 tunable".to_owned()
))
},
RctlState::Enabled => Ok(()),
RctlState::Jailed => {
Err(ExporterError::RctlUnavailable(
"Jail Exporter cannot run within a jail".to_owned()
))
},
RctlState::NotPresent => {
Err(ExporterError::RctlUnavailable(
"Support not present in kernel; see rctl(8) \
for details".to_owned()
))
},
}
}
fn is_running_as_root<U: Users>(users: &mut U) -> Result<(), ExporterError> {
debug!("Ensuring that we're running as root");
match users.get_effective_uid() {
0 => Ok(()),
_ => Err(ExporterError::NotRunningAsRoot),
}
}
#[cfg(feature = "bcrypt_cmd")]
fn bcrypt_cmd(matches: &clap::ArgMatches) -> Result<(), ExporterError> {
let cost: u32 = matches.value_of("COST")
.expect("no bcrypt cost given")
.parse()
.expect("couldn't parse cost as u32");
let random = matches.is_present("RANDOM");
let password = match matches.value_of("PASSWORD") {
Some(password) => password.into(),
None => {
if random {
let length: usize = matches.value_of("LENGTH")
.expect("no password length given")
.parse()
.expect("couldn't parse length as usize");
thread_rng()
.sample_iter(&Alphanumeric)
.take(length)
.map(char::from)
.collect()
}
else {
Password::new()
.with_prompt("Password")
.with_confirmation(
"Confirm password",
"Password mismatch",
)
.interact()?
}
},
};
let hash = bcrypt::hash(&password, cost)?;
if random {
println!("Password: {}", password);
}
println!("Hash: {}", hash);
Ok(())
}
#[cfg(feature = "rc_script")]
fn output_rc_script() {
debug!("Dumping rc(8) script to stdout");
let output = RC_SCRIPT.replace("%%PREFIX%%", "/usr/local");
println!("{}", output);
}
#[actix_web::main]
async fn main() -> Result<(), ExporterError> {
env_logger::init();
let matches = cli::parse_args();
#[cfg(feature = "rc_script")]
if matches.is_present("RC_SCRIPT") {
output_rc_script();
::std::process::exit(0);
}
#[cfg(feature = "bcrypt_cmd")]
if let Some(subcmd) = matches.subcommand_matches("bcrypt") {
bcrypt_cmd(subcmd)?;
::std::process::exit(0);
}
is_running_as_root(&mut UsersCache::new())?;
is_racct_rctl_available()?;
if let Some(output_path) = matches.value_of("OUTPUT_FILE_PATH") {
debug!("output.file-path: {}", output_path);
let exporter = FileExporter::new(output_path);
return exporter.export();
}
let bind_address = matches.value_of("WEB_LISTEN_ADDRESS").ok_or_else(|| {
ExporterError::ArgNotSet("web.listen-address".to_owned())
})?.to_owned();
debug!("web.listen-address: {}", bind_address);
let telemetry_path = matches.value_of("WEB_TELEMETRY_PATH").ok_or_else(|| {
ExporterError::ArgNotSet("web.telemetry-path".to_owned())
})?.to_owned();
debug!("web.telemetry-path: {}", telemetry_path);
#[allow(unused_mut)]
let mut server = httpd::Server::new()
.bind_address(bind_address)
.telemetry_path(telemetry_path);
#[cfg(feature = "auth")]
{
if let Some(path) = matches.value_of("WEB_AUTH_CONFIG") {
let config = BasicAuthConfig::from_yaml(path)?;
server = server.auth_config(config);
}
}
let exporter = Box::new(Exporter::new());
server.run(exporter).await?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn is_running_as_root_ok() {
use users::mock::{
Group,
MockUsers,
User,
};
use users::os::unix::UserExt;
let mut users = MockUsers::with_current_uid(0);
let user = User::new(0, "root", 0).with_home_dir("/root");
users.add_user(user);
users.add_group(Group::new(0, "root"));
let is_root = is_running_as_root(&mut users).unwrap();
let ok = ();
assert_eq!(is_root, ok);
}
#[test]
fn is_running_as_non_root() {
use users::mock::{
Group,
MockUsers,
User,
};
use users::os::unix::UserExt;
let mut users = MockUsers::with_current_uid(10000);
let user = User::new(10000, "ferris", 10000).with_home_dir("/ferris");
users.add_user(user);
users.add_group(Group::new(10000, "ferris"));
let is_root = is_running_as_root(&mut users);
assert!(is_root.is_err());
}
}