]> git.proxmox.com Git - proxmox-backup.git/blob - proxmox-rest-server/src/lib.rs
d72936c2ebcaea3a7ce3faaa848b4f4e81b1a496
[proxmox-backup.git] / proxmox-rest-server / src / lib.rs
1 //! # Proxmox REST server
2 //!
3 //! This module provides convenient building blocks to implement a
4 //! REST server.
5 //!
6 //! ## Features
7 //!
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)
12 //! * supports separate access and authentication log files
13 //! * extra control socket to trigger management operations
14 //! - logfile rotation
15 //! - worker task management
16 //! * generic interface to authenticate user
17
18 use std::sync::atomic::{Ordering, AtomicBool};
19 use std::future::Future;
20 use std::pin::Pin;
21
22 use anyhow::{bail, format_err, Error};
23 use nix::unistd::Pid;
24 use hyper::{Body, Response, Method};
25 use http::request::Parts;
26 use http::HeaderMap;
27
28 use proxmox::tools::fd::Fd;
29 use proxmox::sys::linux::procfs::PidStat;
30 use proxmox::api::UserInformation;
31 use proxmox::tools::fs::CreateOptions;
32
33 mod compression;
34 pub use compression::*;
35
36 pub mod daemon;
37
38 pub mod formatter;
39
40 mod environment;
41 pub use environment::*;
42
43 mod state;
44 pub use state::*;
45
46 mod command_socket;
47 pub use command_socket::*;
48
49 mod file_logger;
50 pub use file_logger::{FileLogger, FileLogOptions};
51
52 mod api_config;
53 pub use api_config::ApiConfig;
54
55 mod rest;
56 pub use rest::RestServer;
57
58 mod worker_task;
59 pub use worker_task::*;
60
61 mod h2service;
62 pub use h2service::*;
63
64 /// Authentication Error
65 pub enum AuthError {
66 Generic(Error),
67 NoData,
68 }
69
70 impl From<Error> for AuthError {
71 fn from(err: Error) -> Self {
72 AuthError::Generic(err)
73 }
74 }
75
76 /// User Authentication and index/root page generation methods
77 pub trait ServerAdapter: Send + Sync {
78
79 /// Returns the index/root page
80 fn get_index(
81 &self,
82 rest_env: RestEnvironment,
83 parts: Parts,
84 ) -> Pin<Box<dyn Future<Output = Response<Body>> + Send>>;
85
86 /// Extract user credentials from headers and check them.
87 ///
88 /// If credenthials are valid, returns the username and a
89 /// [UserInformation] object to query additional user data.
90 fn check_auth<'a>(
91 &'a self,
92 headers: &'a HeaderMap,
93 method: &'a Method,
94 ) -> Pin<Box<dyn Future<Output = Result<(String, Box<dyn UserInformation + Sync + Send>), AuthError>> + Send + 'a>>;
95
96 }
97
98 lazy_static::lazy_static!{
99 static ref PID: i32 = unsafe { libc::getpid() };
100 static ref PSTART: u64 = PidStat::read_from_pid(Pid::from_raw(*PID)).unwrap().starttime;
101 }
102
103 /// Retruns the current process ID (see [libc::getpid])
104 ///
105 /// The value is cached at startup (so it is invalid after a fork)
106 pub(crate) fn pid() -> i32 {
107 *PID
108 }
109
110 /// Returns the starttime of the process (see [PidStat])
111 ///
112 /// The value is cached at startup (so it is invalid after a fork)
113 pub(crate) fn pstart() -> u64 {
114 *PSTART
115 }
116
117 /// Helper to write the PID into a file
118 pub fn write_pid(pid_fn: &str) -> Result<(), Error> {
119 let pid_str = format!("{}\n", *PID);
120 proxmox::tools::fs::replace_file(pid_fn, pid_str.as_bytes(), CreateOptions::new())
121 }
122
123 /// Helper to read the PID from a file
124 pub fn read_pid(pid_fn: &str) -> Result<i32, Error> {
125 let pid = proxmox::tools::fs::file_get_contents(pid_fn)?;
126 let pid = std::str::from_utf8(&pid)?.trim();
127 pid.parse().map_err(|err| format_err!("could not parse pid - {}", err))
128 }
129
130 /// Returns the control socket path for a specific process ID.
131 ///
132 /// Note: The control socket always uses @/run/proxmox-backup/ as
133 /// prefix for historic reason. This does not matter because the
134 /// generated path is unique for each ``pid`` anyways.
135 pub fn ctrl_sock_from_pid(pid: i32) -> String {
136 // Note: The control socket always uses @/run/proxmox-backup/ as prefix
137 // for historc reason.
138 format!("\0{}/control-{}.sock", "/run/proxmox-backup", pid)
139 }
140
141 /// Returns the control socket path for this server.
142 pub fn our_ctrl_sock() -> String {
143 ctrl_sock_from_pid(*PID)
144 }
145
146 static SHUTDOWN_REQUESTED: AtomicBool = AtomicBool::new(false);
147
148 /// Request a server shutdown (usually called from [catch_shutdown_signal])
149 pub fn request_shutdown() {
150 SHUTDOWN_REQUESTED.store(true, Ordering::SeqCst);
151 crate::server_shutdown();
152 }
153
154 /// Returns true if there was a shutdown request.
155 #[inline(always)]
156 pub fn shutdown_requested() -> bool {
157 SHUTDOWN_REQUESTED.load(Ordering::SeqCst)
158 }
159
160 /// Raise an error if there was a shutdown request.
161 pub fn fail_on_shutdown() -> Result<(), Error> {
162 if shutdown_requested() {
163 bail!("Server shutdown requested - aborting task");
164 }
165 Ok(())
166 }
167
168 /// safe wrapper for `nix::sys::socket::socketpair` defaulting to `O_CLOEXEC` and guarding the file
169 /// descriptors.
170 pub fn socketpair() -> Result<(Fd, Fd), Error> {
171 use nix::sys::socket;
172 let (pa, pb) = socket::socketpair(
173 socket::AddressFamily::Unix,
174 socket::SockType::Stream,
175 None,
176 socket::SockFlag::SOCK_CLOEXEC,
177 )?;
178 Ok((Fd(pa), Fd(pb)))
179 }
180
181
182 /// Extract a specific cookie from cookie header.
183 /// We assume cookie_name is already url encoded.
184 pub fn extract_cookie(cookie: &str, cookie_name: &str) -> Option<String> {
185 for pair in cookie.split(';') {
186 let (name, value) = match pair.find('=') {
187 Some(i) => (pair[..i].trim(), pair[(i + 1)..].trim()),
188 None => return None, // Cookie format error
189 };
190
191 if name == cookie_name {
192 use percent_encoding::percent_decode;
193 if let Ok(value) = percent_decode(value.as_bytes()).decode_utf8() {
194 return Some(value.into());
195 } else {
196 return None; // Cookie format error
197 }
198 }
199 }
200
201 None
202 }
203
204 /// normalize uri path
205 ///
206 /// Do not allow ".", "..", or hidden files ".XXXX"
207 /// Also remove empty path components
208 pub fn normalize_uri_path(path: &str) -> Result<(String, Vec<&str>), Error> {
209 let items = path.split('/');
210
211 let mut path = String::new();
212 let mut components = vec![];
213
214 for name in items {
215 if name.is_empty() {
216 continue;
217 }
218 if name.starts_with('.') {
219 bail!("Path contains illegal components.");
220 }
221 path.push('/');
222 path.push_str(name);
223 components.push(name);
224 }
225
226 Ok((path, components))
227 }