diff --git a/Makefile b/Makefile index a1af9d51..bade9412 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ SERVICE_BIN := \ proxmox-backup-api \ proxmox-backup-banner \ proxmox-backup-proxy \ - proxmox-daily-update \ + proxmox-daily-update ifeq ($(BUILD_MODE), release) CARGO_BUILD_ARGS += --release @@ -141,6 +141,8 @@ install: $(COMPILED_BINS) install -m755 $(COMPILEDIR)/$(i) $(DESTDIR)$(SBINDIR)/ ; \ install -m644 zsh-completions/_$(i) $(DESTDIR)$(ZSH_COMPL_DEST)/ ;) install -dm755 $(DESTDIR)$(LIBEXECDIR)/proxmox-backup + # install sg-tape-cmd as setuid binary + install -m2755 -mu+s q$(COMPILEDIR)/sg-tape-cmd $(DESTDIR)$(LIBEXECDIR)/proxmox-backup/sg-tape-cmd $(foreach i,$(SERVICE_BIN), \ install -m755 $(COMPILEDIR)/$(i) $(DESTDIR)$(LIBEXECDIR)/proxmox-backup/ ;) $(MAKE) -C www install diff --git a/src/bin/sg-tape-cmd.rs b/src/bin/sg-tape-cmd.rs new file mode 100644 index 00000000..d0fa04ed --- /dev/null +++ b/src/bin/sg-tape-cmd.rs @@ -0,0 +1,155 @@ +/// Tape command implemented using scsi-generic raw commands +/// +/// SCSI-generic command needs root priviledges, so this binary need +/// to be setuid root. +/// +/// This command can use STDIN as tape device handle. + +use std::fs::File; +use std::os::unix::io::{AsRawFd, FromRawFd}; + +use anyhow::{bail, Error}; + +use proxmox::{ + api::{ + api, + cli::*, + RpcEnvironment, + }, +}; + +use proxmox_backup::{ + api2::types::{ + LINUX_DRIVE_PATH_SCHEMA, + LinuxDriveAndMediaStatus, + }, + tape::{ + mam_extract_media_usage, + linux_tape::{ + LinuxTapeHandle, + open_linux_tape_device, + check_tape_is_linux_tape_device, + }, + }, +}; + +fn get_tape_handle(device: Option) -> Result { + + let file = if let Some(device) = device { + open_linux_tape_device(&device)? + } else { + let fd = std::io::stdin().as_raw_fd(); + let file = unsafe { File::from_raw_fd(fd) }; + check_tape_is_linux_tape_device(&file)?; + file + }; + Ok(LinuxTapeHandle::new(file)) +} + +#[api( + input: { + properties: { + device: { + schema: LINUX_DRIVE_PATH_SCHEMA, + optional: true, + }, + }, + }, +)] +/// Tape/Media Status +fn status( + device: Option, +) -> Result<(), Error> { + + let result = proxmox::try_block!({ + let mut handle = get_tape_handle(device)?; + + let drive_status = handle.get_drive_status()?; + + let mam = handle.cartridge_memory()?; + + let usage = mam_extract_media_usage(&mam)?; + + Ok(LinuxDriveAndMediaStatus { + blocksize: drive_status.blocksize, + density: drive_status.density, + status: format!("{:?}", drive_status.status), + file_number: drive_status.file_number, + block_number: drive_status.block_number, + manufactured: usage.manufactured, + bytes_read: usage.bytes_read, + bytes_written: usage.bytes_written, + }) + }).map_err(|err: Error| err.to_string()); + + println!("{}", serde_json::to_string_pretty(&result)?); + + Ok(()) +} + +#[api( + input: { + properties: { + device: { + schema: LINUX_DRIVE_PATH_SCHEMA, + optional: true, + }, + }, + }, +)] +/// Read Cartridge Memory (Medium auxiliary memory attributes) +fn cartridge_memory( + device: Option, +) -> Result<(), Error> { + + let result = proxmox::try_block!({ + let mut handle = get_tape_handle(device)?; + + handle.cartridge_memory() + }).map_err(|err| err.to_string()); + + println!("{}", serde_json::to_string_pretty(&result)?); + + Ok(()) +} + +fn main() -> Result<(), Error> { + + // check if we are user root or backup + let backup_uid = proxmox_backup::backup::backup_user()?.uid; + let backup_gid = proxmox_backup::backup::backup_group()?.gid; + let running_uid = nix::unistd::Uid::current(); + let running_gid = nix::unistd::Gid::current(); + + let effective_uid = nix::unistd::Uid::effective(); + if !effective_uid.is_root() { + bail!("this program needs to be run with setuid root"); + } + + if !running_uid.is_root() { + if running_uid != backup_uid || running_gid != backup_gid { + bail!( + "Not running as backup user or group (got uid {} gid {})", + running_uid, running_gid, + ); + } + } + + let cmd_def = CliCommandMap::new() + .insert( + "status", + CliCommand::new(&API_METHOD_STATUS) + ) + .insert( + "cartridge-memory", + CliCommand::new(&API_METHOD_CARTRIDGE_MEMORY) + ) + ; + + let mut rpcenv = CliEnvironment::new(); + rpcenv.set_auth_id(Some(String::from("root@pam"))); + + run_cli_command(cmd_def, rpcenv, None); + + Ok(()) +} diff --git a/src/tape/drive/linux_tape.rs b/src/tape/drive/linux_tape.rs index 60d6d067..d3d35377 100644 --- a/src/tape/drive/linux_tape.rs +++ b/src/tape/drive/linux_tape.rs @@ -1,6 +1,6 @@ use std::fs::{OpenOptions, File}; use std::os::unix::fs::OpenOptionsExt; -use std::os::unix::io::AsRawFd; +use std::os::unix::io::{AsRawFd, FromRawFd}; use std::convert::TryFrom; use anyhow::{bail, format_err, Error}; @@ -9,6 +9,7 @@ use nix::fcntl::{fcntl, FcntlArg, OFlag}; use proxmox::sys::error::SysResult; use crate::{ + tools::run_command, api2::types::{ TapeDensity, MamAttribute, @@ -237,8 +238,22 @@ impl LinuxTapeHandle { } /// Read Cartridge Memory (MAM Attributes) + /// + /// Note: Only 'root' user may run RAW SG commands, so we need to + /// spawn setuid binary 'sg-tape-cmd'. pub fn cartridge_memory(&mut self) -> Result, Error> { - read_mam_attributes(&mut self.file) + + if nix::unistd::Uid::effective().is_root() { + return read_mam_attributes(&mut self.file); + } + + let mut command = std::process::Command::new( + "/usr/lib/x86_64-linux-gnu/proxmox-backup/sg-tape-cmd"); + command.args(&["cartridge-memory"]); + command.stdin(unsafe { std::process::Stdio::from_raw_fd(self.file.as_raw_fd())}); + let output = run_command(command, None)?; + let result: Result, String> = serde_json::from_str(&output)?; + result.map_err(|err| format_err!("{}", err)) } }