use std::env; use std::fs; use std::io::prelude::*; use std::ffi::OsString; use std::process::{Command, Stdio, Output}; use regex::Regex; use users::get_current_username; fn exit(error: String) { eprintln!("error: {}", error); std::process::exit(0); } fn main() { let superuser = OsString::from("root"); match get_current_username() { Some(user) => match user { _ if user == superuser => parse_command(), _ => exit("Must be run as root.".to_string()), } None => exit("The current user does not exist.".to_string()), } } fn parse_command() { let args: Vec = env::args().collect(); match args.get(1) { Some(p) => match p { _ if p == "setup-user" => setup_user(args[2].to_string(), args[3].to_string()), _ if p == "reload-nginx" => reload_nginx(), _ if p == "reload-tor" => reload_tor(), _ if p == "reload-twins" => reload_twins(), _ if p == "gemini-new-certificate" => gemini_new_certificate(args[2].to_string()), _ if p == "le-install" => le_install(args[2].to_string()), _ if p == "export-tor" => export_tor(args[2].to_string(), args[3].to_string()), _ => exit("This subcommand doesn't exists.".to_string()), } None => exit("You must specify a subcommand.".to_string()), } } fn gemini_new_certificate(domain: String) { let mut common_name: String = "/CN=".to_owned(); common_name += &domain.to_string(); let mut key_file: String = "/var/local/twins/tls/".to_owned(); key_file += &domain.to_string(); key_file += &".key".to_string().to_owned(); let mut cert_file: String = "/var/local/twins/tls/".to_owned(); cert_file += &domain.to_string(); cert_file += &".crt".to_string().to_owned(); let output = Command::new("/usr/bin/openssl") .arg("req") .arg("-subj") .arg(common_name) .arg("-new") .arg("-newkey") .arg("ED25519") .arg("-days") .arg("3650") .arg("-nodes") .arg("-x509") .arg("-keyout") .arg(&key_file) .arg("-out") .arg(&cert_file) .output() .expect("failed to execute process"); print_output(output); let output = Command::new("/usr/bin/chmod") .arg("400") .arg(&key_file) .output() .expect("Failed to change key file mode to 400"); print_output(output); let output = Command::new("/usr/bin/chown") .arg("twins:twins") .arg(key_file) .output() .expect("Failed to chown key file to twins:twins"); print_output(output); let output = Command::new("/usr/bin/chmod") .arg("400") .arg(&cert_file) .output() .expect("Failed to change key file mode to 400"); print_output(output); let output = Command::new("/usr/bin/chown") .arg("twins:twins") .arg(cert_file) .output() .expect("Failed to chown key file to twins:twins"); print_output(output); } fn le_install(domain: String) { let output = Command::new("/usr/bin/certbot") .arg("certonly") .arg("--nginx") // Using ECDSA //.arg("--key-type") //.arg("ecdsa") //.arg("--elliptic-curve") //.arg("secp384r1") // Using RSA .arg("--key-type") .arg("rsa") .arg("--rsa-key-size") .arg("3072") .arg("-d") .arg(&domain) .output() .expect("failed to execute process"); print_output(output); } fn export_tor(username: String, dir: String) { if is_string_lowercase(username.to_string()) { if is_string_lowercase(dir.to_string()) { let mut src_path: String = "/var/lib/tor-instances/niver/keys/".to_owned(); src_path += &dir.to_string(); src_path += &"/hostname".to_string().to_owned(); let mut dest_path: String = "/srv/ht/".to_owned(); dest_path += &username.to_string(); dest_path += &"/ht/".to_string().to_owned(); dest_path += &dir.to_string(); dest_path += &"/hostname".to_string().to_owned(); match fs::copy(src_path, &dest_path) { Err(why) => panic!("Error while copying file (fs::copy) : {}", why), Ok(process) => process, }; let output = Command::new("/usr/bin/chown") .arg("php-niver:ht") .arg(&dest_path) .output() .expect("failed to execute process"); print_output(output); let output = Command::new("/usr/bin/chmod") .arg("440") .arg(dest_path) .output() .expect("failed to execute process"); print_output(output); } else { exit("The dirname must be composed only of lowercase letters.".to_string()); } } else { exit("The username must be composed only of lowercase letters.".to_string()); } } fn reload_tor() { let output = Command::new("/usr/bin/systemctl") .arg("reload") .arg("tor@niver") .output() .expect("Error while reloading Tor config"); print_output(output); } fn reload_nginx() { let output = Command::new("/usr/bin/systemctl") .arg("reload") .arg("nginx") .output() .expect("Error while reloading Nginx config"); print_output(output); } fn reload_twins() { let output = Command::new("/usr/bin/systemctl") .arg("reload") .arg("twins") .output() .expect("Error while reloading Twins"); print_output(output); } fn setup_user(username: String, password: String) { if username.chars().count() < 32 { if password.chars().count() < 1024 { if is_string_lowercase(username.to_string()) { let username1 = &username; let username2 = &username; let username3 = &username; let username4 = &username; newser(username1.to_string()); pwd(username2.to_string(), password); chown_root(username3.to_string()); quota(username4.to_string()); } else { exit("The username must be composed only of lowercase letters.".to_string()); } } else { exit("The password must be shorter than 1024 characters.".to_string()); } } else { exit("The username must be shorter than 32 characters.".to_string()); } } // Creates a new user in the group 'ht', which is available only over SFTP fn newser(username: String) { let output = Command::new("/usr/sbin/useradd") .arg(&username) .arg("--create-home") .arg("--skel") .arg("/usr/local/share/niver/skel") .arg("--base-dir") .arg("/srv/ht") .arg("--gid") .arg("ht") .arg("--shell") .arg("/usr/sbin/nologin") .output() .expect("failed to execute process"); print_output(output); } // Changes password of the newly created user fn pwd(username: String, password: String) { // line must be in the form username:password let mut line: String = username.to_string(); line += &":".to_string().to_owned(); line += &password.to_owned(); let process = match Command::new("/usr/sbin/chpasswd") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn() { Err(why) => panic!("couldn't spawn chpasswd: {}", why), Ok(process) => process, }; match process.stdin.unwrap().write_all(line.as_bytes()) { Err(why) => panic!("couldn't write to chpasswd stdin: {}", why), Ok(_) => println!("sent username:password to chpasswd"), } let mut s = String::new(); match process.stdout.unwrap().read_to_string(&mut s) { Err(why) => panic!("couldn't read chpasswd stdout: {}", why), Ok(_) => print!("chpasswd responded with:\n{}", s), } } // Chown /srv/ht/username to root:root fn chown_root(username: String) { let mut path = "/srv/ht/".to_string(); path += &username; let output = Command::new("/usr/bin/chown") .arg("root:root") .arg(&path) .output() .expect("Failed to chown /srv/ht/ to root:root"); print_output(output); let output = Command::new("/usr/bin/chmod") .arg("755") .arg(path) .output() .expect("Failed to chmod /srv/ht/ to 755"); print_output(output); } // Set disk usage limit to the user by copying another user quota fn quota(username: String) { let output = Command::new("/usr/sbin/edquota") .arg("-p") .arg("niver-quota") .arg(&username) .output() .expect("failed to execute process"); print_output(output); } fn is_string_lowercase(stri: String) -> bool { let re = Regex::new("^[[:lower:]]+$").unwrap(); let matching = re.is_match(&stri); if matching { return true; } else { return false; } } fn print_output(output: Output) { println!("status: {}", output.status); println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); assert!(output.status.success()); }