]> git.proxmox.com Git - proxmox-backup.git/blob - src/tools/mod.rs
2d2d923ab4d5ae74777f52918f535ad64fbe4358
[proxmox-backup.git] / src / tools / mod.rs
1 //! Tools and utilities
2 //!
3 //! This is a collection of small and useful tools.
4 use std::any::Any;
5 use std::os::unix::io::RawFd;
6
7 use anyhow::{bail, format_err, Error};
8 use openssl::hash::{hash, DigestBytes, MessageDigest};
9
10 pub use proxmox::tools::fd::Fd;
11 use proxmox::tools::fs::{create_path, CreateOptions};
12
13 use proxmox_http::{
14 client::SimpleHttp,
15 client::SimpleHttpOptions,
16 ProxyConfig,
17 };
18
19 pub use pbs_tools::json;
20 pub use pbs_tools::nom;
21 pub use pbs_tools::{run_command, command_output, command_output_as_string};
22 pub use pbs_tools::process_locker::{
23 ProcessLocker, ProcessLockExclusiveGuard, ProcessLockSharedGuard
24 };
25
26 pub mod apt;
27 pub mod async_io;
28 pub mod compression;
29 pub mod config;
30 pub mod cpio;
31 pub mod daemon;
32 pub mod disks;
33 pub mod fuse_loop;
34
35 mod memcom;
36 pub use memcom::Memcom;
37
38 pub mod logrotate;
39 pub mod loopdev;
40 pub mod serde_filter;
41 pub mod statistics;
42 pub mod subscription;
43 pub mod systemd;
44 pub mod ticket;
45 pub mod sgutils2;
46 pub mod paperkey;
47
48 pub mod parallel_handler;
49 pub use parallel_handler::ParallelHandler;
50
51 mod file_logger;
52 pub use file_logger::{FileLogger, FileLogOptions};
53
54 pub use pbs_tools::broadcast_future::{BroadcastData, BroadcastFuture};
55 pub use pbs_tools::ops::ControlFlow;
56
57 /// The `BufferedRead` trait provides a single function
58 /// `buffered_read`. It returns a reference to an internal buffer. The
59 /// purpose of this traid is to avoid unnecessary data copies.
60 pub trait BufferedRead {
61 /// This functions tries to fill the internal buffers, then
62 /// returns a reference to the available data. It returns an empty
63 /// buffer if `offset` points to the end of the file.
64 fn buffered_read(&mut self, offset: u64) -> Result<&[u8], Error>;
65 }
66
67 /// Shortcut for md5 sums.
68 pub fn md5sum(data: &[u8]) -> Result<DigestBytes, Error> {
69 hash(MessageDigest::md5(), data).map_err(Error::from)
70 }
71
72 pub fn get_hardware_address() -> Result<String, Error> {
73 static FILENAME: &str = "/etc/ssh/ssh_host_rsa_key.pub";
74
75 let contents = proxmox::tools::fs::file_get_contents(FILENAME)
76 .map_err(|e| format_err!("Error getting host key - {}", e))?;
77 let digest = md5sum(&contents)
78 .map_err(|e| format_err!("Error digesting host key - {}", e))?;
79
80 Ok(proxmox::tools::bin_to_hex(&digest).to_uppercase())
81 }
82
83 pub fn assert_if_modified(digest1: &str, digest2: &str) -> Result<(), Error> {
84 if digest1 != digest2 {
85 bail!("detected modified configuration - file changed by other user? Try again.");
86 }
87 Ok(())
88 }
89
90 /// Extract a specific cookie from cookie header.
91 /// We assume cookie_name is already url encoded.
92 pub fn extract_cookie(cookie: &str, cookie_name: &str) -> Option<String> {
93 for pair in cookie.split(';') {
94 let (name, value) = match pair.find('=') {
95 Some(i) => (pair[..i].trim(), pair[(i + 1)..].trim()),
96 None => return None, // Cookie format error
97 };
98
99 if name == cookie_name {
100 use percent_encoding::percent_decode;
101 if let Ok(value) = percent_decode(value.as_bytes()).decode_utf8() {
102 return Some(value.into());
103 } else {
104 return None; // Cookie format error
105 }
106 }
107 }
108
109 None
110 }
111
112 /// Detect modified configuration files
113 ///
114 /// This function fails with a reasonable error message if checksums do not match.
115 pub fn detect_modified_configuration_file(digest1: &[u8;32], digest2: &[u8;32]) -> Result<(), Error> {
116 if digest1 != digest2 {
117 bail!("detected modified configuration - file changed by other user? Try again.");
118 }
119 Ok(())
120 }
121
122 /// normalize uri path
123 ///
124 /// Do not allow ".", "..", or hidden files ".XXXX"
125 /// Also remove empty path components
126 pub fn normalize_uri_path(path: &str) -> Result<(String, Vec<&str>), Error> {
127 let items = path.split('/');
128
129 let mut path = String::new();
130 let mut components = vec![];
131
132 for name in items {
133 if name.is_empty() {
134 continue;
135 }
136 if name.starts_with('.') {
137 bail!("Path contains illegal components.");
138 }
139 path.push('/');
140 path.push_str(name);
141 components.push(name);
142 }
143
144 Ok((path, components))
145 }
146
147 pub fn fd_change_cloexec(fd: RawFd, on: bool) -> Result<(), Error> {
148 use nix::fcntl::{fcntl, FdFlag, F_GETFD, F_SETFD};
149 let mut flags = FdFlag::from_bits(fcntl(fd, F_GETFD)?)
150 .ok_or_else(|| format_err!("unhandled file flags"))?; // nix crate is stupid this way...
151 flags.set(FdFlag::FD_CLOEXEC, on);
152 fcntl(fd, F_SETFD(flags))?;
153 Ok(())
154 }
155
156 static mut SHUTDOWN_REQUESTED: bool = false;
157
158 pub fn request_shutdown() {
159 unsafe {
160 SHUTDOWN_REQUESTED = true;
161 }
162 crate::server::server_shutdown();
163 }
164
165 #[inline(always)]
166 pub fn shutdown_requested() -> bool {
167 unsafe { SHUTDOWN_REQUESTED }
168 }
169
170 pub fn fail_on_shutdown() -> Result<(), Error> {
171 if shutdown_requested() {
172 bail!("Server shutdown requested - aborting task");
173 }
174 Ok(())
175 }
176
177 /// safe wrapper for `nix::unistd::pipe2` defaulting to `O_CLOEXEC` and guarding the file
178 /// descriptors.
179 pub fn pipe() -> Result<(Fd, Fd), Error> {
180 let (pin, pout) = nix::unistd::pipe2(nix::fcntl::OFlag::O_CLOEXEC)?;
181 Ok((Fd(pin), Fd(pout)))
182 }
183
184 /// safe wrapper for `nix::sys::socket::socketpair` defaulting to `O_CLOEXEC` and guarding the file
185 /// descriptors.
186 pub fn socketpair() -> Result<(Fd, Fd), Error> {
187 use nix::sys::socket;
188 let (pa, pb) = socket::socketpair(
189 socket::AddressFamily::Unix,
190 socket::SockType::Stream,
191 None,
192 socket::SockFlag::SOCK_CLOEXEC,
193 )?;
194 Ok((Fd(pa), Fd(pb)))
195 }
196
197
198 /// An easy way to convert types to Any
199 ///
200 /// Mostly useful to downcast trait objects (see RpcEnvironment).
201 pub trait AsAny {
202 fn as_any(&self) -> &dyn Any;
203 }
204
205 impl<T: Any> AsAny for T {
206 fn as_any(&self) -> &dyn Any {
207 self
208 }
209 }
210
211 /// The default 2 hours are far too long for PBS
212 pub const PROXMOX_BACKUP_TCP_KEEPALIVE_TIME: u32 = 120;
213 pub const DEFAULT_USER_AGENT_STRING: &'static str = "proxmox-backup-client/1.0";
214
215 /// Returns a new instance of `SimpleHttp` configured for PBS usage.
216 pub fn pbs_simple_http(proxy_config: Option<ProxyConfig>) -> SimpleHttp {
217 let options = SimpleHttpOptions {
218 proxy_config,
219 user_agent: Some(DEFAULT_USER_AGENT_STRING.to_string()),
220 tcp_keepalive: Some(PROXMOX_BACKUP_TCP_KEEPALIVE_TIME),
221 ..Default::default()
222 };
223
224 SimpleHttp::with_options(options)
225 }
226
227 pub fn setup_safe_path_env() {
228 std::env::set_var("PATH", "/sbin:/bin:/usr/sbin:/usr/bin");
229 // Make %ENV safer - as suggested by https://perldoc.perl.org/perlsec.html
230 for name in &["IFS", "CDPATH", "ENV", "BASH_ENV"] {
231 std::env::remove_var(name);
232 }
233 }
234
235 /// Create the base run-directory.
236 ///
237 /// This exists to fixate the permissions for the run *base* directory while allowing intermediate
238 /// directories after it to have different permissions.
239 pub fn create_run_dir() -> Result<(), Error> {
240 let backup_user = crate::backup::backup_user()?;
241 let opts = CreateOptions::new()
242 .owner(backup_user.uid)
243 .group(backup_user.gid);
244 let _: bool = create_path(pbs_buildcfg::PROXMOX_BACKUP_RUN_DIR_M!(), None, Some(opts))?;
245 Ok(())
246 }