2 use ::serde
::{Deserialize, Serialize}
;
4 use proxmox
::api
::{api, schema::*}
;
5 use proxmox
::const_regex
;
6 use proxmox
::{IPRE, IPV4RE, IPV6RE, IPV4OCTET, IPV6H16, IPV6LS32}
;
8 // File names: may not contain slashes, may not start with "."
9 pub const FILENAME_FORMAT
: ApiStringFormat
= ApiStringFormat
::VerifyFn(|name
| {
10 if name
.starts_with('
.'
) {
11 bail
!("file names may not start with '.'");
13 if name
.contains('
/'
) {
14 bail
!("file names may not contain slashes");
19 macro_rules
! DNS_LABEL { () => (r"(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)") }
20 macro_rules
! DNS_NAME { () => (concat!(r"(?:", DNS_LABEL!() , r"\.)*", DNS_LABEL!())) }
22 // we only allow a limited set of characters
23 // colon is not allowed, because we store usernames in
24 // colon separated lists)!
25 // slash is not allowed because it is used as pve API delimiter
26 // also see "man useradd"
27 macro_rules
! USER_NAME_REGEX_STR { () => (r"(?:[^\s:/[:cntrl:]]+)") }
29 macro_rules
! PROXMOX_SAFE_ID_REGEX_STR { () => (r"(?:[A-Za-z0-9_][A-Za-z0-9._\-]*)") }
32 pub IP_FORMAT_REGEX
= IPRE
!();
33 pub SHA256_HEX_REGEX
= r
"^[a-f0-9]{64}$"; // fixme: define in common_regex ?
34 pub SYSTEMD_DATETIME_REGEX
= r
"^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$"; // fixme: define in common_regex ?
36 pub PASSWORD_REGEX
= r
"^[[:^cntrl:]]*$"; // everything but control characters
38 /// Regex for safe identifiers.
41 /// [article](https://dwheeler.com/essays/fixing-unix-linux-filenames.html)
42 /// contains further information why it is reasonable to restict
43 /// names this way. This is not only useful for filenames, but for
44 /// any identifier command line tools work with.
45 pub PROXMOX_SAFE_ID_REGEX
= concat
!(r
"^", PROXMOX_SAFE_ID_REGEX_STR
!(), r
"$");
47 pub SINGLE_LINE_COMMENT_REGEX
= r
"^[[:^cntrl:]]*$";
49 pub HOSTNAME_REGEX
= r
"^(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)$";
51 pub DNS_NAME_REGEX
= concat
!(r
"^", DNS_NAME
!(), r
"$");
53 pub DNS_NAME_OR_IP_REGEX
= concat
!(r
"^", DNS_NAME
!(), "|", IPRE
!(), r
"$");
55 pub PROXMOX_USER_ID_REGEX
= concat
!(r
"^", USER_NAME_REGEX_STR
!(), r
"@", PROXMOX_SAFE_ID_REGEX_STR
!(), r
"$");
58 pub const SYSTEMD_DATETIME_FORMAT
: ApiStringFormat
=
59 ApiStringFormat
::Pattern(&SYSTEMD_DATETIME_REGEX
);
61 pub const IP_FORMAT
: ApiStringFormat
=
62 ApiStringFormat
::Pattern(&IP_FORMAT_REGEX
);
64 pub const PVE_CONFIG_DIGEST_FORMAT
: ApiStringFormat
=
65 ApiStringFormat
::Pattern(&SHA256_HEX_REGEX
);
67 pub const PROXMOX_SAFE_ID_FORMAT
: ApiStringFormat
=
68 ApiStringFormat
::Pattern(&PROXMOX_SAFE_ID_REGEX
);
70 pub const SINGLE_LINE_COMMENT_FORMAT
: ApiStringFormat
=
71 ApiStringFormat
::Pattern(&SINGLE_LINE_COMMENT_REGEX
);
73 pub const HOSTNAME_FORMAT
: ApiStringFormat
=
74 ApiStringFormat
::Pattern(&HOSTNAME_REGEX
);
76 pub const DNS_NAME_FORMAT
: ApiStringFormat
=
77 ApiStringFormat
::Pattern(&DNS_NAME_REGEX
);
79 pub const DNS_NAME_OR_IP_FORMAT
: ApiStringFormat
=
80 ApiStringFormat
::Pattern(&DNS_NAME_OR_IP_REGEX
);
82 pub const PROXMOX_USER_ID_FORMAT
: ApiStringFormat
=
83 ApiStringFormat
::Pattern(&PROXMOX_USER_ID_REGEX
);
85 pub const PASSWORD_FORMAT
: ApiStringFormat
=
86 ApiStringFormat
::Pattern(&PASSWORD_REGEX
);
89 pub const PROXMOX_CONFIG_DIGEST_SCHEMA
: Schema
= StringSchema
::new(r
#"\
90 Prevent changes if current configuration file has different SHA256 digest.
91 This can be used to prevent concurrent modifications.
94 .format(&PVE_CONFIG_DIGEST_FORMAT
)
98 pub const CHUNK_DIGEST_FORMAT
: ApiStringFormat
=
99 ApiStringFormat
::Pattern(&SHA256_HEX_REGEX
);
101 pub const CHUNK_DIGEST_SCHEMA
: Schema
= StringSchema
::new("Chunk digest (SHA256).")
102 .format(&CHUNK_DIGEST_FORMAT
)
105 pub const NODE_SCHEMA
: Schema
= StringSchema
::new("Node name (or 'localhost')")
106 .format(&ApiStringFormat
::VerifyFn(|node
| {
107 if node
== "localhost" || node
== proxmox
::tools
::nodename() {
110 bail
!("no such node '{}'", node
);
115 pub const SEARCH_DOMAIN_SCHEMA
: Schema
=
116 StringSchema
::new("Search domain for host-name lookup.").schema();
118 pub const FIRST_DNS_SERVER_SCHEMA
: Schema
=
119 StringSchema
::new("First name server IP address.")
123 pub const SECOND_DNS_SERVER_SCHEMA
: Schema
=
124 StringSchema
::new("Second name server IP address.")
128 pub const THIRD_DNS_SERVER_SCHEMA
: Schema
=
129 StringSchema
::new("Third name server IP address.")
133 pub const BACKUP_ARCHIVE_NAME_SCHEMA
: Schema
=
134 StringSchema
::new("Backup archive name.")
135 .format(&PROXMOX_SAFE_ID_FORMAT
)
138 pub const BACKUP_TYPE_SCHEMA
: Schema
=
139 StringSchema
::new("Backup type.")
140 .format(&ApiStringFormat
::Enum(&["vm", "ct", "host"]))
143 pub const BACKUP_ID_SCHEMA
: Schema
=
144 StringSchema
::new("Backup ID.")
145 .format(&PROXMOX_SAFE_ID_FORMAT
)
148 pub const BACKUP_TIME_SCHEMA
: Schema
=
149 IntegerSchema
::new("Backup time (Unix epoch.)")
150 .minimum(1_547_797_308)
153 pub const UPID_SCHEMA
: Schema
= StringSchema
::new("Unique Process/Task ID.")
157 pub const DATASTORE_SCHEMA
: Schema
= StringSchema
::new("Datastore name.")
158 .format(&PROXMOX_SAFE_ID_FORMAT
)
163 pub const REMOTE_ID_SCHEMA
: Schema
= StringSchema
::new("Remote ID.")
164 .format(&PROXMOX_SAFE_ID_FORMAT
)
169 pub const SINGLE_LINE_COMMENT_SCHEMA
: Schema
= StringSchema
::new("Comment (single line).")
170 .format(&SINGLE_LINE_COMMENT_FORMAT
)
173 pub const HOSTNAME_SCHEMA
: Schema
= StringSchema
::new("Hostname (as defined in RFC1123).")
174 .format(&HOSTNAME_FORMAT
)
177 pub const DNS_NAME_OR_IP_SCHEMA
: Schema
= StringSchema
::new("DNS name or IP address.")
178 .format(&DNS_NAME_OR_IP_FORMAT
)
181 pub const PROXMOX_AUTH_REALM_SCHEMA
: Schema
= StringSchema
::new("Authentication domain ID")
182 .format(&PROXMOX_SAFE_ID_FORMAT
)
187 pub const PROXMOX_USER_ID_SCHEMA
: Schema
= StringSchema
::new("User ID")
188 .format(&PROXMOX_USER_ID_FORMAT
)
194 // Complex type definitions
199 schema
: BACKUP_TYPE_SCHEMA
,
202 schema
: BACKUP_ID_SCHEMA
,
205 schema
: BACKUP_TIME_SCHEMA
,
212 schema
: BACKUP_ARCHIVE_NAME_SCHEMA
217 #[derive(Serialize, Deserialize)]
218 #[serde(rename_all="kebab-case")]
219 /// Basic information about a backup group.
220 pub struct GroupListItem
{
221 pub backup_type
: String
, // enum
222 pub backup_id
: String
,
223 pub last_backup
: i64,
224 /// Number of contained snapshots
225 pub backup_count
: u64,
226 /// List of contained archive files.
227 pub files
: Vec
<String
>,
233 schema
: BACKUP_TYPE_SCHEMA
,
236 schema
: BACKUP_ID_SCHEMA
,
239 schema
: BACKUP_TIME_SCHEMA
,
243 schema
: BACKUP_ARCHIVE_NAME_SCHEMA
248 #[derive(Serialize, Deserialize)]
249 #[serde(rename_all="kebab-case")]
250 /// Basic information about backup snapshot.
251 pub struct SnapshotListItem
{
252 pub backup_type
: String
, // enum
253 pub backup_id
: String
,
254 pub backup_time
: i64,
255 /// List of contained archive files.
256 pub files
: Vec
<String
>,
257 /// Overall snapshot size (sum of all archive sizes).
258 #[serde(skip_serializing_if="Option::is_none")]
259 pub size
: Option
<u64>,
265 schema
: BACKUP_ARCHIVE_NAME_SCHEMA
,
269 #[derive(Serialize, Deserialize)]
270 #[serde(rename_all="kebab-case")]
271 /// Basic information about archive files inside a backup snapshot.
272 pub struct BackupContent
{
273 pub filename
: String
,
274 /// Archive size (from backup manifest).
275 #[serde(skip_serializing_if="Option::is_none")]
276 pub size
: Option
<u64>,
280 #[derive(Serialize, Deserialize)]
281 /// Storage space usage information.
282 pub struct StorageStatus
{
283 /// Total space (bytes).
285 /// Used space (bytes).
287 /// Available space (bytes).
294 fn test_proxmox_user_id_schema() -> Result
<(), Error
> {
296 let schema
= PROXMOX_USER_ID_SCHEMA
;
298 let invalid_user_ids
= [
303 "xx x@test", // contains space
304 "xx\nx@test", // contains control character
305 "x:xx@test", // contains collon
306 "xx/x@test", // contains slash
307 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@test", // too long
310 for name
in invalid_user_ids
.iter() {
311 if let Ok(_
) = parse_simple_value(name
, &schema
) {
312 bail
!("test userid '{}' failed - got Ok() while expection an error.", name
);
316 let valid_user_ids
= [
320 "xxx@_T_E_S_T-it.com",
321 "x_x-x.x@test-it.com",
324 for name
in valid_user_ids
.iter() {
325 let v
= match parse_simple_value(name
, &schema
) {
328 bail
!("unable to parse userid '{}' - {}", name
, err
);
332 if v
!= serde_json
::json
!(name
) {
333 bail
!("unable to parse userid '{}' - got wrong value {:?}", name
, v
);