]>
Commit | Line | Data |
---|---|---|
4ebf0eab | 1 | use failure::*; |
fc189b19 | 2 | use ::serde::{Deserialize, Serialize}; |
4ebf0eab | 3 | |
fc189b19 | 4 | use proxmox::api::{api, const_regex, schema::*}; |
255f378a DM |
5 | use proxmox::tools::*; // required to use IPRE!() macro ??? |
6 | ||
7 | // File names: may not contain slashes, may not start with "." | |
8 | pub const FILENAME_FORMAT: ApiStringFormat = ApiStringFormat::VerifyFn(|name| { | |
9 | if name.starts_with('.') { | |
10 | bail!("file names may not start with '.'"); | |
11 | } | |
12 | if name.contains('/') { | |
13 | bail!("file names may not contain slashes"); | |
14 | } | |
15 | Ok(()) | |
16 | }); | |
17 | ||
b25f313d DM |
18 | macro_rules! DNS_LABEL { () => (r"(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)") } |
19 | macro_rules! DNS_NAME { () => (concat!(r"(?:", DNS_LABEL!() , r"\.)*", DNS_LABEL!())) } | |
255f378a | 20 | |
163dc16c DM |
21 | // we only allow a limited set of characters |
22 | // colon is not allowed, because we store usernames in | |
23 | // colon separated lists)! | |
24 | // slash is not allowed because it is used as pve API delimiter | |
25 | // also see "man useradd" | |
ae62c4fe | 26 | macro_rules! USER_NAME_REGEX_STR { () => (r"(?:[^\s:/[:cntrl:]]+)") } |
163dc16c DM |
27 | |
28 | macro_rules! PROXMOX_SAFE_ID_REGEX_STR { () => (r"(?:[A-Za-z0-9_][A-Za-z0-9._\-]*)") } | |
29 | ||
255f378a DM |
30 | const_regex!{ |
31 | pub IP_FORMAT_REGEX = IPRE!(); | |
32 | pub SHA256_HEX_REGEX = r"^[a-f0-9]{64}$"; // fixme: define in common_regex ? | |
33 | pub SYSTEMD_DATETIME_REGEX = r"^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$"; // fixme: define in common_regex ? | |
d0adf270 | 34 | |
da4a15a3 DM |
35 | pub PASSWORD_REGEX = r"^[[:^cntrl:]]*$"; // everything but control characters |
36 | ||
d0adf270 DM |
37 | /// Regex for safe identifiers. |
38 | /// | |
39 | /// This | |
40 | /// [article](https://dwheeler.com/essays/fixing-unix-linux-filenames.html) | |
41 | /// contains further information why it is reasonable to restict | |
42 | /// names this way. This is not only useful for filenames, but for | |
43 | /// any identifier command line tools work with. | |
163dc16c | 44 | pub PROXMOX_SAFE_ID_REGEX = concat!(r"^", PROXMOX_SAFE_ID_REGEX_STR!(), r"$"); |
454c13ed DM |
45 | |
46 | pub SINGLE_LINE_COMMENT_REGEX = r"^[[:^cntrl:]]*$"; | |
b25f313d DM |
47 | |
48 | pub HOSTNAME_REGEX = r"^(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)$"; | |
49 | ||
ae62c4fe | 50 | pub DNS_NAME_REGEX = concat!(r"^", DNS_NAME!(), r"$"); |
b25f313d | 51 | |
ae62c4fe | 52 | pub DNS_NAME_OR_IP_REGEX = concat!(r"^", DNS_NAME!(), "|", IPRE!(), r"$"); |
163dc16c DM |
53 | |
54 | pub PROXMOX_USER_ID_REGEX = concat!(r"^", USER_NAME_REGEX_STR!(), r"@", PROXMOX_SAFE_ID_REGEX_STR!(), r"$"); | |
255f378a | 55 | } |
4ebf0eab | 56 | |
255f378a DM |
57 | pub const SYSTEMD_DATETIME_FORMAT: ApiStringFormat = |
58 | ApiStringFormat::Pattern(&SYSTEMD_DATETIME_REGEX); | |
4ebf0eab | 59 | |
255f378a DM |
60 | pub const IP_FORMAT: ApiStringFormat = |
61 | ApiStringFormat::Pattern(&IP_FORMAT_REGEX); | |
bbf9e7e9 | 62 | |
255f378a DM |
63 | pub const PVE_CONFIG_DIGEST_FORMAT: ApiStringFormat = |
64 | ApiStringFormat::Pattern(&SHA256_HEX_REGEX); | |
65 | ||
d0adf270 DM |
66 | pub const PROXMOX_SAFE_ID_FORMAT: ApiStringFormat = |
67 | ApiStringFormat::Pattern(&PROXMOX_SAFE_ID_REGEX); | |
68 | ||
454c13ed DM |
69 | pub const SINGLE_LINE_COMMENT_FORMAT: ApiStringFormat = |
70 | ApiStringFormat::Pattern(&SINGLE_LINE_COMMENT_REGEX); | |
71 | ||
b25f313d DM |
72 | pub const HOSTNAME_FORMAT: ApiStringFormat = |
73 | ApiStringFormat::Pattern(&HOSTNAME_REGEX); | |
74 | ||
75 | pub const DNS_NAME_FORMAT: ApiStringFormat = | |
76 | ApiStringFormat::Pattern(&DNS_NAME_REGEX); | |
77 | ||
78 | pub const DNS_NAME_OR_IP_FORMAT: ApiStringFormat = | |
79 | ApiStringFormat::Pattern(&DNS_NAME_OR_IP_REGEX); | |
80 | ||
163dc16c DM |
81 | pub const PROXMOX_USER_ID_FORMAT: ApiStringFormat = |
82 | ApiStringFormat::Pattern(&PROXMOX_USER_ID_REGEX); | |
83 | ||
7e7b781a DM |
84 | pub const PASSWORD_FORMAT: ApiStringFormat = |
85 | ApiStringFormat::Pattern(&PASSWORD_REGEX); | |
86 | ||
454c13ed | 87 | |
255f378a DM |
88 | pub const PVE_CONFIG_DIGEST_SCHEMA: Schema = StringSchema::new(r#"\ |
89 | Prevent changes if current configuration file has different SHA256 digest. | |
90 | This can be used to prevent concurrent modifications. | |
91 | "# | |
92 | ) | |
93 | .format(&PVE_CONFIG_DIGEST_FORMAT) | |
94 | .schema(); | |
95 | ||
96 | ||
97 | pub const CHUNK_DIGEST_FORMAT: ApiStringFormat = | |
98 | ApiStringFormat::Pattern(&SHA256_HEX_REGEX); | |
99 | ||
100 | pub const CHUNK_DIGEST_SCHEMA: Schema = StringSchema::new("Chunk digest (SHA256).") | |
101 | .format(&CHUNK_DIGEST_FORMAT) | |
102 | .schema(); | |
103 | ||
104 | pub const NODE_SCHEMA: Schema = StringSchema::new("Node name (or 'localhost')") | |
105 | .format(&ApiStringFormat::VerifyFn(|node| { | |
106 | if node == "localhost" || node == proxmox::tools::nodename() { | |
107 | Ok(()) | |
108 | } else { | |
109 | bail!("no such node '{}'", node); | |
110 | } | |
111 | })) | |
112 | .schema(); | |
113 | ||
114 | pub const SEARCH_DOMAIN_SCHEMA: Schema = | |
115 | StringSchema::new("Search domain for host-name lookup.").schema(); | |
116 | ||
117 | pub const FIRST_DNS_SERVER_SCHEMA: Schema = | |
118 | StringSchema::new("First name server IP address.") | |
119 | .format(&IP_FORMAT) | |
120 | .schema(); | |
121 | ||
122 | pub const SECOND_DNS_SERVER_SCHEMA: Schema = | |
123 | StringSchema::new("Second name server IP address.") | |
124 | .format(&IP_FORMAT) | |
125 | .schema(); | |
126 | ||
127 | pub const THIRD_DNS_SERVER_SCHEMA: Schema = | |
128 | StringSchema::new("Third name server IP address.") | |
129 | .format(&IP_FORMAT) | |
130 | .schema(); | |
131 | ||
132 | pub const BACKUP_ARCHIVE_NAME_SCHEMA: Schema = | |
133 | StringSchema::new("Backup archive name.") | |
1ae5677d | 134 | .format(&PROXMOX_SAFE_ID_FORMAT) |
255f378a DM |
135 | .schema(); |
136 | ||
137 | pub const BACKUP_TYPE_SCHEMA: Schema = | |
138 | StringSchema::new("Backup type.") | |
139 | .format(&ApiStringFormat::Enum(&["vm", "ct", "host"])) | |
140 | .schema(); | |
141 | ||
142 | pub const BACKUP_ID_SCHEMA: Schema = | |
143 | StringSchema::new("Backup ID.") | |
1ae5677d | 144 | .format(&PROXMOX_SAFE_ID_FORMAT) |
255f378a DM |
145 | .schema(); |
146 | ||
147 | pub const BACKUP_TIME_SCHEMA: Schema = | |
148 | IntegerSchema::new("Backup time (Unix epoch.)") | |
149 | .minimum(1_547_797_308) | |
150 | .schema(); | |
5830c205 DM |
151 | |
152 | pub const UPID_SCHEMA: Schema = StringSchema::new("Unique Process/Task ID.") | |
153 | .max_length(256) | |
154 | .schema(); | |
66c49c21 DM |
155 | |
156 | pub const DATASTORE_SCHEMA: Schema = StringSchema::new("Datastore name.") | |
d0adf270 | 157 | .format(&PROXMOX_SAFE_ID_FORMAT) |
688fbe07 | 158 | .min_length(3) |
66c49c21 DM |
159 | .max_length(32) |
160 | .schema(); | |
fc189b19 | 161 | |
167971ed DM |
162 | pub const REMOTE_ID_SCHEMA: Schema = StringSchema::new("Remote ID.") |
163 | .format(&PROXMOX_SAFE_ID_FORMAT) | |
164 | .min_length(3) | |
165 | .max_length(32) | |
166 | .schema(); | |
167 | ||
454c13ed DM |
168 | pub const SINGLE_LINE_COMMENT_SCHEMA: Schema = StringSchema::new("Comment (single line).") |
169 | .format(&SINGLE_LINE_COMMENT_FORMAT) | |
170 | .schema(); | |
fc189b19 | 171 | |
b25f313d DM |
172 | pub const HOSTNAME_SCHEMA: Schema = StringSchema::new("Hostname (as defined in RFC1123).") |
173 | .format(&HOSTNAME_FORMAT) | |
174 | .schema(); | |
175 | ||
176 | pub const DNS_NAME_OR_IP_SCHEMA: Schema = StringSchema::new("DNS name or IP address.") | |
177 | .format(&DNS_NAME_OR_IP_FORMAT) | |
178 | .schema(); | |
179 | ||
163dc16c DM |
180 | pub const PROXMOX_AUTH_REALM_SCHEMA: Schema = StringSchema::new("Authentication domain ID") |
181 | .format(&PROXMOX_SAFE_ID_FORMAT) | |
182 | .min_length(3) | |
183 | .max_length(32) | |
184 | .schema(); | |
185 | ||
186 | pub const PROXMOX_USER_ID_SCHEMA: Schema = StringSchema::new("User ID") | |
187 | .format(&PROXMOX_USER_ID_FORMAT) | |
188 | .min_length(3) | |
189 | .max_length(64) | |
190 | .schema(); | |
191 | ||
fc189b19 DM |
192 | |
193 | // Complex type definitions | |
194 | ||
195 | #[api( | |
fc189b19 DM |
196 | properties: { |
197 | "backup-type": { | |
198 | schema: BACKUP_TYPE_SCHEMA, | |
199 | }, | |
200 | "backup-id": { | |
201 | schema: BACKUP_ID_SCHEMA, | |
202 | }, | |
203 | "backup-time": { | |
204 | schema: BACKUP_TIME_SCHEMA, | |
205 | }, | |
71da3d6a DM |
206 | files: { |
207 | items: { | |
208 | schema: BACKUP_ARCHIVE_NAME_SCHEMA | |
209 | }, | |
210 | }, | |
fc189b19 DM |
211 | }, |
212 | )] | |
213 | #[derive(Serialize, Deserialize)] | |
214 | #[serde(rename_all="kebab-case")] | |
71da3d6a | 215 | /// Basic information about backup snapshot. |
fc189b19 DM |
216 | pub struct SnapshotListItem { |
217 | pub backup_type: String, // enum | |
218 | pub backup_id: String, | |
219 | pub backup_time: i64, | |
71da3d6a | 220 | /// List of contained archive files. |
fc189b19 | 221 | pub files: Vec<String>, |
71da3d6a | 222 | /// Overall snapshot size (sum of all archive sizes). |
fc189b19 DM |
223 | #[serde(skip_serializing_if="Option::is_none")] |
224 | pub size: Option<u64>, | |
225 | } | |
ff620a3d DM |
226 | |
227 | ||
228 | // Regression tests | |
229 | ||
230 | #[test] | |
231 | fn test_proxmox_user_id_schema() -> Result<(), Error> { | |
232 | ||
233 | let schema = PROXMOX_USER_ID_SCHEMA; | |
234 | ||
235 | let invalid_user_ids = [ | |
236 | "x", // too short | |
237 | "xx", // too short | |
238 | "xxx", // no realm | |
239 | "xxx@", // no realm | |
240 | "xx x@test", // contains space | |
241 | "xx\nx@test", // contains control character | |
242 | "x:xx@test", // contains collon | |
243 | "xx/x@test", // contains slash | |
244 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@test", // too long | |
245 | ]; | |
246 | ||
247 | for name in invalid_user_ids.iter() { | |
248 | if let Ok(_) = parse_simple_value(name, &schema) { | |
249 | bail!("test userid '{}' failed - got Ok() while expection an error.", name); | |
250 | } | |
251 | } | |
252 | ||
253 | let valid_user_ids = [ | |
254 | "xxx@y", | |
255 | "name@y", | |
256 | "xxx@test-it.com", | |
257 | "xxx@_T_E_S_T-it.com", | |
258 | "x_x-x.x@test-it.com", | |
259 | ]; | |
260 | ||
261 | for name in valid_user_ids.iter() { | |
262 | let v = match parse_simple_value(name, &schema) { | |
263 | Ok(v) => v, | |
264 | Err(err) => { | |
265 | bail!("unable to parse userid '{}' - {}", name, err); | |
266 | } | |
267 | }; | |
268 | ||
269 | if v != serde_json::json!(name) { | |
270 | bail!("unable to parse userid '{}' - got wrong value {:?}", name, v); | |
271 | } | |
272 | } | |
273 | ||
274 | Ok(()) | |
275 | } |