]> git.proxmox.com Git - proxmox.git/blame - proxmox-rest-server/src/lib.rs
rest-server: support configuring the privileged connection
[proxmox.git] / proxmox-rest-server / src / lib.rs
CommitLineData
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 18use std::fmt;
8bd961ac 19use std::os::unix::io::{FromRawFd, OwnedFd};
2bda552b 20use std::sync::atomic::{AtomicBool, Ordering};
51d84f98
DM
21
22use anyhow::{bail, format_err, Error};
2bda552b 23use nix::unistd::Pid;
51d84f98 24
93625e4f 25use proxmox_sys::fs::CreateOptions;
2bda552b 26use proxmox_sys::linux::procfs::PidStat;
51d84f98 27
fbe0de85
DM
28mod compression;
29pub use compression::*;
30
51d84f98 31pub mod daemon;
962553d2 32
dc28aa1a 33pub mod formatter;
ca7a2616 34
ba04dfb9
DM
35mod environment;
36pub use environment::*;
37
ca7a2616
DM
38mod state;
39pub use state::*;
40
41mod command_socket;
42pub use command_socket::*;
43
44mod file_logger;
2bda552b 45pub use file_logger::{FileLogOptions, FileLogger};
ca7a2616
DM
46
47mod api_config;
2bba40f6 48pub use api_config::{ApiConfig, AuthError, AuthHandler, IndexHandler, UnixAcceptor};
ca7a2616 49
1d60abf9 50mod rest;
7d292699 51pub use rest::{Redirector, RestServer};
1d60abf9 52
e2ac53e3
WB
53pub mod connection;
54
e8c124fe
DM
55mod worker_task;
56pub use worker_task::*;
57
85ec987a
DM
58mod h2service;
59pub use h2service::*;
60
2bda552b 61lazy_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 69pub(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 76pub(crate) fn pstart() -> u64 {
e8c124fe
DM
77 *PSTART
78}
79
9cb2c97c 80/// Helper to write the PID into a file
e8c124fe
DM
81pub 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 87pub 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
99pub 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
106pub fn our_ctrl_sock() -> String {
107 ctrl_sock_from_pid(*PID)
108}
109
5b724780 110static SHUTDOWN_REQUESTED: AtomicBool = AtomicBool::new(false);
ca7a2616 111
9cb2c97c 112/// Request a server shutdown (usually called from [catch_shutdown_signal])
ca7a2616 113pub 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)]
120pub 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
125pub 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 134fn 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.
147pub 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.
169pub 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
181pub 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)]
205pub struct IllegalPathComponents;
206
207impl std::error::Error for IllegalPathComponents {}
208
209impl 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.
217pub 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}