1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include "alloc-util.h"
4 #include "hexdecoct.h"
5 #include "list.h"
6 #include "parse-util.h"
7 #include "path-util.h"
8 #include "stdio-util.h"
9 #include "string-util.h"
10 #include "sysupdate-pattern.h"
11 #include "sysupdate-util.h"
12
13 typedef enum PatternElementType {
14 PATTERN_LITERAL,
15 PATTERN_VERSION,
16 PATTERN_PARTITION_UUID,
17 PATTERN_PARTITION_FLAGS,
18 PATTERN_MTIME,
19 PATTERN_MODE,
20 PATTERN_SIZE,
21 PATTERN_TRIES_DONE,
22 PATTERN_TRIES_LEFT,
23 PATTERN_NO_AUTO,
24 PATTERN_READ_ONLY,
25 PATTERN_GROWFS,
26 PATTERN_SHA256SUM,
27 _PATTERN_ELEMENT_TYPE_MAX,
28 _PATTERN_ELEMENT_TYPE_INVALID = -EINVAL,
29 } PatternElementType;
30
31 typedef struct PatternElement PatternElement;
32
33 struct PatternElement {
34 PatternElementType type;
35 LIST_FIELDS(PatternElement, elements);
36 char literal[];
37 };
38
pattern_element_free_all(PatternElement * e)39 static PatternElement *pattern_element_free_all(PatternElement *e) {
40 PatternElement *p;
41
42 while ((p = LIST_POP(elements, e)))
43 free(p);
44
45 return NULL;
46 }
47
48 DEFINE_TRIVIAL_CLEANUP_FUNC(PatternElement*, pattern_element_free_all);
49
pattern_element_type_from_char(char c)50 static PatternElementType pattern_element_type_from_char(char c) {
51 switch (c) {
52 case 'v':
53 return PATTERN_VERSION;
54 case 'u':
55 return PATTERN_PARTITION_UUID;
56 case 'f':
57 return PATTERN_PARTITION_FLAGS;
58 case 't':
59 return PATTERN_MTIME;
60 case 'm':
61 return PATTERN_MODE;
62 case 's':
63 return PATTERN_SIZE;
64 case 'd':
65 return PATTERN_TRIES_DONE;
66 case 'l':
67 return PATTERN_TRIES_LEFT;
68 case 'a':
69 return PATTERN_NO_AUTO;
70 case 'r':
71 return PATTERN_READ_ONLY;
72 case 'g':
73 return PATTERN_GROWFS;
74 case 'h':
75 return PATTERN_SHA256SUM;
76 default:
77 return _PATTERN_ELEMENT_TYPE_INVALID;
78 }
79 }
80
valid_char(char x)81 static bool valid_char(char x) {
82
83 /* Let's refuse control characters here, and let's reserve some characters typically used in pattern
84 * languages so that we can use them later, possibly. */
85
86 if ((unsigned) x < ' ' || x >= 127)
87 return false;
88
89 return !IN_SET(x, '$', '*', '?', '[', ']', '!', '\\', '/', '|');
90 }
91
pattern_split(const char * pattern,PatternElement ** ret)92 static int pattern_split(
93 const char *pattern,
94 PatternElement **ret) {
95
96 _cleanup_(pattern_element_free_allp) PatternElement *first = NULL;
97 bool at = false, last_literal = true;
98 PatternElement *last = NULL;
99 uint64_t mask_found = 0;
100 size_t l, k = 0;
101
102 assert(pattern);
103
104 l = strlen(pattern);
105
106 for (const char *e = pattern; *e != 0; e++) {
107 if (*e == '@') {
108 if (!at) {
109 at = true;
110 continue;
111 }
112
113 /* Two at signs in a sequence, write out one */
114 at = false;
115
116 } else if (at) {
117 PatternElementType t;
118 uint64_t bit;
119
120 t = pattern_element_type_from_char(*e);
121 if (t < 0)
122 return log_debug_errno(t, "Unknown pattern field marker '@%c'.", *e);
123
124 bit = UINT64_C(1) << t;
125 if (mask_found & bit)
126 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Pattern field marker '@%c' appears twice in pattern.", *e);
127
128 /* We insist that two pattern field markers are separated by some literal string that
129 * we can use to separate the fields when parsing. */
130 if (!last_literal)
131 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Found two pattern field markers without separating literal.");
132
133 if (ret) {
134 PatternElement *z;
135
136 z = malloc(offsetof(PatternElement, literal));
137 if (!z)
138 return -ENOMEM;
139
140 z->type = t;
141 LIST_INSERT_AFTER(elements, first, last, z);
142 last = z;
143 }
144
145 mask_found |= bit;
146 last_literal = at = false;
147 continue;
148 }
149
150 if (!valid_char(*e))
151 return log_debug_errno(SYNTHETIC_ERRNO(EBADRQC), "Invalid character 0x%0x in pattern, refusing.", *e);
152
153 last_literal = true;
154
155 if (!ret)
156 continue;
157
158 if (!last || last->type != PATTERN_LITERAL) {
159 PatternElement *z;
160
161 z = malloc0(offsetof(PatternElement, literal) + l + 1); /* l is an upper bound to all literal elements */
162 if (!z)
163 return -ENOMEM;
164
165 z->type = PATTERN_LITERAL;
166 k = 0;
167
168 LIST_INSERT_AFTER(elements, first, last, z);
169 last = z;
170 }
171
172 assert(last);
173 assert(last->type == PATTERN_LITERAL);
174
175 last->literal[k++] = *e;
176 }
177
178 if (at)
179 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Trailing @ character found, refusing.");
180 if (!(mask_found & (UINT64_C(1) << PATTERN_VERSION)))
181 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Version field marker '@v' not specified in pattern, refusing.");
182
183 if (ret)
184 *ret = TAKE_PTR(first);
185
186 return 0;
187 }
188
pattern_match(const char * pattern,const char * s,InstanceMetadata * ret)189 int pattern_match(const char *pattern, const char *s, InstanceMetadata *ret) {
190 _cleanup_(instance_metadata_destroy) InstanceMetadata found = INSTANCE_METADATA_NULL;
191 _cleanup_(pattern_element_free_allp) PatternElement *elements = NULL;
192 const char *p;
193 int r;
194
195 assert(pattern);
196 assert(s);
197
198 r = pattern_split(pattern, &elements);
199 if (r < 0)
200 return r;
201
202 p = s;
203 LIST_FOREACH(elements, e, elements) {
204 _cleanup_free_ char *t = NULL;
205 const char *n;
206
207 if (e->type == PATTERN_LITERAL) {
208 const char *k;
209
210 /* Skip literal fields */
211 k = startswith(p, e->literal);
212 if (!k)
213 goto nope;
214
215 p = k;
216 continue;
217 }
218
219 if (e->elements_next) {
220 /* The next element must be literal, as we use it to determine where to split */
221 assert(e->elements_next->type == PATTERN_LITERAL);
222
223 n = strstr(p, e->elements_next->literal);
224 if (!n)
225 goto nope;
226
227 } else
228 /* End of the string */
229 assert_se(n = strchr(p, 0));
230 t = strndup(p, n - p);
231 if (!t)
232 return -ENOMEM;
233
234 switch (e->type) {
235
236 case PATTERN_VERSION:
237 if (!version_is_valid(t)) {
238 log_debug("Version string is not valid, refusing: %s", t);
239 goto nope;
240 }
241
242 assert(!found.version);
243 found.version = TAKE_PTR(t);
244 break;
245
246 case PATTERN_PARTITION_UUID: {
247 sd_id128_t id;
248
249 if (sd_id128_from_string(t, &id) < 0)
250 goto nope;
251
252 assert(!found.partition_uuid_set);
253 found.partition_uuid = id;
254 found.partition_uuid_set = true;
255 break;
256 }
257
258 case PATTERN_PARTITION_FLAGS: {
259 uint64_t f;
260
261 if (safe_atoux64(t, &f) < 0)
262 goto nope;
263
264 if (found.partition_flags_set && found.partition_flags != f)
265 goto nope;
266
267 assert(!found.partition_flags_set);
268 found.partition_flags = f;
269 found.partition_flags_set = true;
270 break;
271 }
272
273 case PATTERN_MTIME: {
274 uint64_t v;
275
276 if (safe_atou64(t, &v) < 0)
277 goto nope;
278 if (v == USEC_INFINITY) /* Don't permit our internal special infinity value */
279 goto nope;
280 if (v / 1000000U > TIME_T_MAX) /* Make sure this fits in a timespec structure */
281 goto nope;
282
283 assert(found.mtime == USEC_INFINITY);
284 found.mtime = v;
285 break;
286 }
287
288 case PATTERN_MODE: {
289 mode_t m;
290
291 r = parse_mode(t, &m);
292 if (r < 0)
293 goto nope;
294 if (m & ~0775) /* Don't allow world-writable files or suid files to be generated this way */
295 goto nope;
296
297 assert(found.mode == MODE_INVALID);
298 found.mode = m;
299 break;
300 }
301
302 case PATTERN_SIZE: {
303 uint64_t u;
304
305 r = safe_atou64(t, &u);
306 if (r < 0)
307 goto nope;
308 if (u == UINT64_MAX)
309 goto nope;
310
311 assert(found.size == UINT64_MAX);
312 found.size = u;
313 break;
314 }
315
316 case PATTERN_TRIES_DONE: {
317 uint64_t u;
318
319 r = safe_atou64(t, &u);
320 if (r < 0)
321 goto nope;
322 if (u == UINT64_MAX)
323 goto nope;
324
325 assert(found.tries_done == UINT64_MAX);
326 found.tries_done = u;
327 break;
328 }
329
330 case PATTERN_TRIES_LEFT: {
331 uint64_t u;
332
333 r = safe_atou64(t, &u);
334 if (r < 0)
335 goto nope;
336 if (u == UINT64_MAX)
337 goto nope;
338
339 assert(found.tries_left == UINT64_MAX);
340 found.tries_left = u;
341 break;
342 }
343
344 case PATTERN_NO_AUTO:
345 r = parse_boolean(t);
346 if (r < 0)
347 goto nope;
348
349 assert(found.no_auto < 0);
350 found.no_auto = r;
351 break;
352
353 case PATTERN_READ_ONLY:
354 r = parse_boolean(t);
355 if (r < 0)
356 goto nope;
357
358 assert(found.read_only < 0);
359 found.read_only = r;
360 break;
361
362 case PATTERN_GROWFS:
363 r = parse_boolean(t);
364 if (r < 0)
365 goto nope;
366
367 assert(found.growfs < 0);
368 found.growfs = r;
369 break;
370
371 case PATTERN_SHA256SUM: {
372 _cleanup_free_ void *d = NULL;
373 size_t l;
374
375 if (strlen(t) != sizeof(found.sha256sum) * 2)
376 goto nope;
377
378 r = unhexmem(t, sizeof(found.sha256sum) * 2, &d, &l);
379 if (r == -ENOMEM)
380 return r;
381 if (r < 0)
382 goto nope;
383
384 assert(!found.sha256sum_set);
385 assert(l == sizeof(found.sha256sum));
386 memcpy(found.sha256sum, d, l);
387 found.sha256sum_set = true;
388 break;
389 }
390
391 default:
392 assert_se("unexpected pattern element");
393 }
394
395 p = n;
396 }
397
398 if (ret) {
399 *ret = found;
400 found = (InstanceMetadata) INSTANCE_METADATA_NULL;
401 }
402
403 return true;
404
405 nope:
406 if (ret)
407 *ret = (InstanceMetadata) INSTANCE_METADATA_NULL;
408
409 return false;
410 }
411
pattern_match_many(char ** patterns,const char * s,InstanceMetadata * ret)412 int pattern_match_many(char **patterns, const char *s, InstanceMetadata *ret) {
413 _cleanup_(instance_metadata_destroy) InstanceMetadata found = INSTANCE_METADATA_NULL;
414 int r;
415
416 STRV_FOREACH(p, patterns) {
417 r = pattern_match(*p, s, &found);
418 if (r < 0)
419 return r;
420 if (r > 0) {
421 if (ret) {
422 *ret = found;
423 found = (InstanceMetadata) INSTANCE_METADATA_NULL;
424 }
425
426 return true;
427 }
428 }
429
430 if (ret)
431 *ret = (InstanceMetadata) INSTANCE_METADATA_NULL;
432
433 return false;
434 }
435
pattern_valid(const char * pattern)436 int pattern_valid(const char *pattern) {
437 int r;
438
439 r = pattern_split(pattern, NULL);
440 if (r == -EINVAL)
441 return false;
442 if (r < 0)
443 return r;
444
445 return true;
446 }
447
pattern_format(const char * pattern,const InstanceMetadata * fields,char ** ret)448 int pattern_format(
449 const char *pattern,
450 const InstanceMetadata *fields,
451 char **ret) {
452
453 _cleanup_(pattern_element_free_allp) PatternElement *elements = NULL;
454 _cleanup_free_ char *j = NULL;
455 int r;
456
457 assert(pattern);
458 assert(fields);
459 assert(ret);
460
461 r = pattern_split(pattern, &elements);
462 if (r < 0)
463 return r;
464
465 LIST_FOREACH(elements, e, elements) {
466
467 switch (e->type) {
468
469 case PATTERN_LITERAL:
470 if (!strextend(&j, e->literal))
471 return -ENOMEM;
472
473 break;
474
475 case PATTERN_VERSION:
476 if (!fields->version)
477 return -ENXIO;
478
479 if (!strextend(&j, fields->version))
480 return -ENOMEM;
481 break;
482
483 case PATTERN_PARTITION_UUID: {
484 char formatted[SD_ID128_STRING_MAX];
485
486 if (!fields->partition_uuid_set)
487 return -ENXIO;
488
489 if (!strextend(&j, sd_id128_to_string(fields->partition_uuid, formatted)))
490 return -ENOMEM;
491
492 break;
493 }
494
495 case PATTERN_PARTITION_FLAGS:
496 if (!fields->partition_flags_set)
497 return -ENXIO;
498
499 r = strextendf(&j, "%" PRIx64, fields->partition_flags);
500 if (r < 0)
501 return r;
502
503 break;
504
505 case PATTERN_MTIME:
506 if (fields->mtime == USEC_INFINITY)
507 return -ENXIO;
508
509 r = strextendf(&j, "%" PRIu64, fields->mtime);
510 if (r < 0)
511 return r;
512
513 break;
514
515 case PATTERN_MODE:
516 if (fields->mode == MODE_INVALID)
517 return -ENXIO;
518
519 r = strextendf(&j, "%03o", fields->mode);
520 if (r < 0)
521 return r;
522
523 break;
524
525 case PATTERN_SIZE:
526 if (fields->size == UINT64_MAX)
527 return -ENXIO;
528
529 r = strextendf(&j, "%" PRIu64, fields->size);
530 if (r < 0)
531 return r;
532 break;
533
534 case PATTERN_TRIES_DONE:
535 if (fields->tries_done == UINT64_MAX)
536 return -ENXIO;
537
538 r = strextendf(&j, "%" PRIu64, fields->tries_done);
539 if (r < 0)
540 return r;
541 break;
542
543 case PATTERN_TRIES_LEFT:
544 if (fields->tries_left == UINT64_MAX)
545 return -ENXIO;
546
547 r = strextendf(&j, "%" PRIu64, fields->tries_left);
548 if (r < 0)
549 return r;
550 break;
551
552 case PATTERN_NO_AUTO:
553 if (fields->no_auto < 0)
554 return -ENXIO;
555
556 if (!strextend(&j, one_zero(fields->no_auto)))
557 return -ENOMEM;
558
559 break;
560
561 case PATTERN_READ_ONLY:
562 if (fields->read_only < 0)
563 return -ENXIO;
564
565 if (!strextend(&j, one_zero(fields->read_only)))
566 return -ENOMEM;
567
568 break;
569
570 case PATTERN_GROWFS:
571 if (fields->growfs < 0)
572 return -ENXIO;
573
574 if (!strextend(&j, one_zero(fields->growfs)))
575 return -ENOMEM;
576
577 break;
578
579 case PATTERN_SHA256SUM: {
580 _cleanup_free_ char *h = NULL;
581
582 if (!fields->sha256sum_set)
583 return -ENXIO;
584
585 h = hexmem(fields->sha256sum, sizeof(fields->sha256sum));
586 if (!h)
587 return -ENOMEM;
588
589 if (!strextend(&j, h))
590 return -ENOMEM;
591
592 break;
593 }
594
595 default:
596 assert_not_reached();
597 }
598 }
599
600 *ret = TAKE_PTR(j);
601 return 0;
602 }
603