]>
Commit | Line | Data |
---|---|---|
58eba821 DM |
1 | //! # Proxmox REST server |
2 | //! | |
3 | //! This module provides convenient building blocks to implement a | |
4 | //! REST server. | |
5 | //! | |
6 | //! ## Features | |
7 | //! | |
58eba821 DM |
8 | //! * highly threaded code, uses Rust async |
9 | //! * static API definitions using schemas | |
10 | //! * restartable systemd daemons using `systemd_notify` | |
11 | //! * support for long running worker tasks (threads or async tokio tasks) | |
a6c0ec35 DM |
12 | //! * supports separate access and authentication log files |
13 | //! * extra control socket to trigger management operations | |
58eba821 DM |
14 | //! - logfile rotation |
15 | //! - worker task management | |
16 | //! * generic interface to authenticate user | |
17 | ||
4a5360ae | 18 | use std::fmt; |
8bd961ac | 19 | use std::os::unix::io::{FromRawFd, OwnedFd}; |
2bda552b | 20 | use std::sync::atomic::{AtomicBool, Ordering}; |
51d84f98 DM |
21 | |
22 | use anyhow::{bail, format_err, Error}; | |
2bda552b | 23 | use nix::unistd::Pid; |
51d84f98 | 24 | |
93625e4f | 25 | use proxmox_sys::fs::CreateOptions; |
2bda552b | 26 | use proxmox_sys::linux::procfs::PidStat; |
51d84f98 | 27 | |
fbe0de85 DM |
28 | mod compression; |
29 | pub use compression::*; | |
30 | ||
51d84f98 | 31 | pub mod daemon; |
962553d2 | 32 | |
dc28aa1a | 33 | pub mod formatter; |
ca7a2616 | 34 | |
ba04dfb9 DM |
35 | mod environment; |
36 | pub use environment::*; | |
37 | ||
ca7a2616 DM |
38 | mod state; |
39 | pub use state::*; | |
40 | ||
41 | mod command_socket; | |
42 | pub use command_socket::*; | |
43 | ||
44 | mod file_logger; | |
2bda552b | 45 | pub use file_logger::{FileLogOptions, FileLogger}; |
ca7a2616 DM |
46 | |
47 | mod api_config; | |
2bba40f6 | 48 | pub use api_config::{ApiConfig, AuthError, AuthHandler, IndexHandler, UnixAcceptor}; |
ca7a2616 | 49 | |
1d60abf9 | 50 | mod rest; |
7d292699 | 51 | pub use rest::{Redirector, RestServer}; |
1d60abf9 | 52 | |
e2ac53e3 WB |
53 | pub mod connection; |
54 | ||
e8c124fe DM |
55 | mod worker_task; |
56 | pub use worker_task::*; | |
57 | ||
85ec987a DM |
58 | mod h2service; |
59 | pub use h2service::*; | |
60 | ||
2bda552b | 61 | lazy_static::lazy_static! { |
e8c124fe DM |
62 | static ref PID: i32 = unsafe { libc::getpid() }; |
63 | static ref PSTART: u64 = PidStat::read_from_pid(Pid::from_raw(*PID)).unwrap().starttime; | |
64 | } | |
65 | ||
be8f24ff | 66 | /// Returns the current process ID (see [libc::getpid]) |
9cb2c97c DM |
67 | /// |
68 | /// The value is cached at startup (so it is invalid after a fork) | |
6d4e47fb | 69 | pub(crate) fn pid() -> i32 { |
e8c124fe DM |
70 | *PID |
71 | } | |
72 | ||
9cb2c97c DM |
73 | /// Returns the starttime of the process (see [PidStat]) |
74 | /// | |
75 | /// The value is cached at startup (so it is invalid after a fork) | |
6d4e47fb | 76 | pub(crate) fn pstart() -> u64 { |
e8c124fe DM |
77 | *PSTART |
78 | } | |
79 | ||
9cb2c97c | 80 | /// Helper to write the PID into a file |
e8c124fe DM |
81 | pub fn write_pid(pid_fn: &str) -> Result<(), Error> { |
82 | let pid_str = format!("{}\n", *PID); | |
93625e4f | 83 | proxmox_sys::fs::replace_file(pid_fn, pid_str.as_bytes(), CreateOptions::new(), false) |
e8c124fe DM |
84 | } |
85 | ||
9cb2c97c | 86 | /// Helper to read the PID from a file |
e8c124fe | 87 | pub fn read_pid(pid_fn: &str) -> Result<i32, Error> { |
93625e4f | 88 | let pid = proxmox_sys::fs::file_get_contents(pid_fn)?; |
e8c124fe | 89 | let pid = std::str::from_utf8(&pid)?.trim(); |
2bda552b TL |
90 | pid.parse() |
91 | .map_err(|err| format_err!("could not parse pid - {}", err)) | |
e8c124fe DM |
92 | } |
93 | ||
9cb2c97c DM |
94 | /// Returns the control socket path for a specific process ID. |
95 | /// | |
96 | /// Note: The control socket always uses @/run/proxmox-backup/ as | |
97 | /// prefix for historic reason. This does not matter because the | |
98 | /// generated path is unique for each ``pid`` anyways. | |
e8c124fe DM |
99 | pub fn ctrl_sock_from_pid(pid: i32) -> String { |
100 | // Note: The control socket always uses @/run/proxmox-backup/ as prefix | |
101 | // for historc reason. | |
102 | format!("\0{}/control-{}.sock", "/run/proxmox-backup", pid) | |
103 | } | |
104 | ||
9cb2c97c | 105 | /// Returns the control socket path for this server. |
e8c124fe DM |
106 | pub fn our_ctrl_sock() -> String { |
107 | ctrl_sock_from_pid(*PID) | |
108 | } | |
109 | ||
5b724780 | 110 | static SHUTDOWN_REQUESTED: AtomicBool = AtomicBool::new(false); |
ca7a2616 | 111 | |
9cb2c97c | 112 | /// Request a server shutdown (usually called from [catch_shutdown_signal]) |
ca7a2616 | 113 | pub fn request_shutdown() { |
5b724780 | 114 | SHUTDOWN_REQUESTED.store(true, Ordering::SeqCst); |
ca7a2616 DM |
115 | crate::server_shutdown(); |
116 | } | |
117 | ||
9cb2c97c | 118 | /// Returns true if there was a shutdown request. |
ca7a2616 DM |
119 | #[inline(always)] |
120 | pub fn shutdown_requested() -> bool { | |
5b724780 | 121 | SHUTDOWN_REQUESTED.load(Ordering::SeqCst) |
ca7a2616 DM |
122 | } |
123 | ||
9cb2c97c | 124 | /// Raise an error if there was a shutdown request. |
ca7a2616 DM |
125 | pub fn fail_on_shutdown() -> Result<(), Error> { |
126 | if shutdown_requested() { | |
127 | bail!("Server shutdown requested - aborting task"); | |
128 | } | |
129 | Ok(()) | |
130 | } | |
131 | ||
51d84f98 DM |
132 | /// safe wrapper for `nix::sys::socket::socketpair` defaulting to `O_CLOEXEC` and guarding the file |
133 | /// descriptors. | |
01436ae3 | 134 | fn socketpair() -> Result<(OwnedFd, OwnedFd), Error> { |
51d84f98 DM |
135 | use nix::sys::socket; |
136 | let (pa, pb) = socket::socketpair( | |
137 | socket::AddressFamily::Unix, | |
138 | socket::SockType::Stream, | |
139 | None, | |
140 | socket::SockFlag::SOCK_CLOEXEC, | |
141 | )?; | |
8bd961ac | 142 | Ok(unsafe { (OwnedFd::from_raw_fd(pa), OwnedFd::from_raw_fd(pb)) }) |
51d84f98 DM |
143 | } |
144 | ||
cc674416 DM |
145 | /// Extract a specific cookie from cookie header. |
146 | /// We assume cookie_name is already url encoded. | |
147 | pub fn extract_cookie(cookie: &str, cookie_name: &str) -> Option<String> { | |
148 | for pair in cookie.split(';') { | |
149 | let (name, value) = match pair.find('=') { | |
150 | Some(i) => (pair[..i].trim(), pair[(i + 1)..].trim()), | |
151 | None => return None, // Cookie format error | |
152 | }; | |
153 | ||
154 | if name == cookie_name { | |
155 | use percent_encoding::percent_decode; | |
156 | if let Ok(value) = percent_decode(value.as_bytes()).decode_utf8() { | |
157 | return Some(value.into()); | |
158 | } else { | |
159 | return None; // Cookie format error | |
160 | } | |
161 | } | |
162 | } | |
163 | ||
164 | None | |
165 | } | |
166 | ||
645b2ae8 TL |
167 | /// Extract a specific cookie from a HeaderMap's "COOKIE" entry. |
168 | /// We assume cookie_name is already url encoded. | |
169 | pub fn cookie_from_header(headers: &http::HeaderMap, cookie_name: &str) -> Option<String> { | |
170 | if let Some(Ok(cookie)) = headers.get("COOKIE").map(|v| v.to_str()) { | |
bb7018e1 | 171 | extract_cookie(cookie, cookie_name) |
645b2ae8 TL |
172 | } else { |
173 | None | |
174 | } | |
175 | } | |
176 | ||
cc674416 DM |
177 | /// normalize uri path |
178 | /// | |
179 | /// Do not allow ".", "..", or hidden files ".XXXX" | |
180 | /// Also remove empty path components | |
4a5360ae WB |
181 | pub fn normalize_path_with_components( |
182 | path: &str, | |
183 | ) -> Result<(String, Vec<&str>), IllegalPathComponents> { | |
cc674416 DM |
184 | let items = path.split('/'); |
185 | ||
186 | let mut path = String::new(); | |
187 | let mut components = vec![]; | |
188 | ||
189 | for name in items { | |
190 | if name.is_empty() { | |
191 | continue; | |
192 | } | |
193 | if name.starts_with('.') { | |
4a5360ae | 194 | return Err(IllegalPathComponents); |
cc674416 DM |
195 | } |
196 | path.push('/'); | |
197 | path.push_str(name); | |
198 | components.push(name); | |
199 | } | |
200 | ||
201 | Ok((path, components)) | |
202 | } | |
4a5360ae WB |
203 | |
204 | #[derive(Debug)] | |
205 | pub struct IllegalPathComponents; | |
206 | ||
207 | impl std::error::Error for IllegalPathComponents {} | |
208 | ||
209 | impl fmt::Display for IllegalPathComponents { | |
210 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
211 | f.write_str("path contains illegal components") | |
212 | } | |
213 | } | |
214 | ||
215 | /// Normalize a uri path by stripping empty components. | |
216 | /// Components starting with a '.' are illegal. | |
217 | pub fn normalize_path(path: &str) -> Result<String, IllegalPathComponents> { | |
218 | let mut output = String::with_capacity(path.len()); | |
219 | for item in path.split('/') { | |
220 | if item.is_empty() { | |
221 | continue; | |
222 | } | |
223 | ||
224 | if item.starts_with('.') { | |
225 | return Err(IllegalPathComponents); | |
226 | } | |
227 | ||
228 | output.push('/'); | |
229 | output.push_str(item); | |
230 | } | |
231 | Ok(output) | |
232 | } |