1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
5 #include "alloc-util.h"
6 #include "dirent-util.h"
11 #include "memory-util.h"
13 #include "path-util.h"
14 #include "process-util.h"
15 #include "pull-common.h"
18 #include "siphash24.h"
19 #include "string-util.h"
21 #include "tmpfile-util.h"
24 #define FILENAME_ESCAPE "/.#\"\'"
25 #define HASH_URL_THRESHOLD_LENGTH (_POSIX_PATH_MAX - 16)
27 int pull_find_old_etags(
29 const char *image_root
,
41 _cleanup_free_
char *escaped_url
= xescape(url
, FILENAME_ESCAPE
);
45 _cleanup_closedir_
DIR *d
= opendir(image_root
);
47 if (errno
== ENOENT
) {
55 _cleanup_strv_free_
char **ans
= NULL
;
57 FOREACH_DIRENT_ALL(de
, d
, return -errno
) {
58 _cleanup_free_
char *u
= NULL
;
61 if (de
->d_type
!= DT_UNKNOWN
&&
66 a
= startswith(de
->d_name
, prefix
);
72 a
= startswith(a
, escaped_url
);
76 a
= startswith(a
, ".");
81 b
= endswith(de
->d_name
, suffix
);
85 b
= strchr(de
->d_name
, 0);
90 ssize_t l
= cunescape_length(a
, b
- a
, 0, &u
);
92 assert(l
>= INT8_MIN
);
96 if (!http_etag_is_valid(u
))
99 r
= strv_consume(&ans
, TAKE_PTR(u
));
104 *etags
= TAKE_PTR(ans
);
109 static int hash_url(const char *url
, char **ret
) {
111 static const sd_id128_t k
= SD_ID128_ARRAY(df
,89,16,87,01,cc
,42,30,98,ab
,4a
,19,a6
,a5
,63,4f
);
115 h
= siphash24(url
, strlen(url
), k
.bytes
);
116 if (asprintf(ret
, "%"PRIx64
, h
) < 0)
122 int pull_make_path(const char *url
, const char *etag
, const char *image_root
, const char *prefix
, const char *suffix
, char **ret
) {
123 _cleanup_free_
char *escaped_url
= NULL
, *escaped_etag
= NULL
;
130 escaped_url
= xescape(url
, FILENAME_ESCAPE
);
135 escaped_etag
= xescape(etag
, FILENAME_ESCAPE
);
140 path
= strjoin(image_root
, "/", strempty(prefix
), escaped_url
, escaped_etag
? "." : "",
141 strempty(escaped_etag
), strempty(suffix
));
145 /* URLs might make the path longer than the maximum allowed length for a file name.
146 * When that happens, a URL hash is used instead. Paths returned by this function
147 * can be later used with tempfn_random() which adds 16 bytes to the resulting name. */
148 if (strlen(path
) >= HASH_URL_THRESHOLD_LENGTH
) {
149 _cleanup_free_
char *hash
= NULL
;
154 r
= hash_url(url
, &hash
);
158 path
= strjoin(image_root
, "/", strempty(prefix
), hash
, escaped_etag
? "." : "",
159 strempty(escaped_etag
), strempty(suffix
));
168 int pull_make_auxiliary_job(
171 int (*strip_suffixes
)(const char *name
, char **ret
),
175 PullJobOpenDisk on_open_disk
,
176 PullJobFinished on_finished
,
179 _cleanup_free_
char *last_component
= NULL
, *ll
= NULL
, *auxiliary_url
= NULL
;
180 _cleanup_(pull_job_unrefp
) PullJob
*job
= NULL
;
186 assert(strip_suffixes
);
189 r
= import_url_last_component(url
, &last_component
);
193 r
= strip_suffixes(last_component
, &ll
);
197 q
= strjoina(ll
, suffix
);
199 r
= import_url_change_last_component(url
, q
, &auxiliary_url
);
203 r
= pull_job_new(&job
, auxiliary_url
, glue
, userdata
);
207 job
->on_open_disk
= on_open_disk
;
208 job
->on_finished
= on_finished
;
209 job
->compressed_max
= job
->uncompressed_max
= 1ULL * 1024ULL * 1024ULL;
210 job
->calc_checksum
= IN_SET(verify
, IMPORT_VERIFY_CHECKSUM
, IMPORT_VERIFY_SIGNATURE
);
212 *ret
= TAKE_PTR(job
);
216 static bool is_checksum_file(const char *fn
) {
217 /* Returns true if the specified filename refers to a checksum file we grok */
222 return streq(fn
, "SHA256SUMS") || endswith(fn
, ".sha256");
225 static SignatureStyle
signature_style_from_filename(const char *fn
) {
226 /* Returns true if the specified filename refers to a signature file we grok */
229 return _SIGNATURE_STYLE_INVALID
;
231 if (streq(fn
, "SHA256SUMS.gpg"))
232 return SIGNATURE_GPG_PER_DIRECTORY
;
234 if (streq(fn
, "SHA256SUMS.asc"))
235 return SIGNATURE_ASC_PER_DIRECTORY
;
237 if (endswith(fn
, ".sha256.gpg"))
238 return SIGNATURE_GPG_PER_FILE
;
240 if (endswith(fn
, ".sha256.asc"))
241 return SIGNATURE_ASC_PER_FILE
;
243 return _SIGNATURE_STYLE_INVALID
;
246 int pull_make_verification_jobs(
247 PullJob
**ret_checksum_job
,
248 PullJob
**ret_signature_job
,
250 const char *checksum
, /* set if literal checksum verification is requested, in which case 'verify' is set to _IMPORT_VERIFY_INVALID */
253 PullJobFinished on_finished
,
256 _cleanup_(pull_job_unrefp
) PullJob
*checksum_job
= NULL
, *signature_job
= NULL
;
257 _cleanup_free_
char *fn
= NULL
;
260 assert(ret_checksum_job
);
261 assert(ret_signature_job
);
262 assert(verify
== _IMPORT_VERIFY_INVALID
|| verify
< _IMPORT_VERIFY_MAX
);
263 assert(verify
== _IMPORT_VERIFY_INVALID
|| verify
>= 0);
264 assert((verify
< 0) || !checksum
);
268 /* If verification is turned off, or if the checksum to validate is already specified we don't need
269 * to download a checksum file or signature, hence shortcut things */
270 if (verify
== IMPORT_VERIFY_NO
|| checksum
) {
271 *ret_checksum_job
= *ret_signature_job
= NULL
;
275 r
= import_url_last_component(url
, &fn
);
276 if (r
< 0 && r
!= -EADDRNOTAVAIL
) /* EADDRNOTAVAIL means there was no last component, which is OK for
277 * us, we'll just assume it's not a checksum/signature file */
280 /* Acquire the checksum file if verification or signature verification is requested and the main file
281 * to acquire isn't a checksum or signature file anyway */
282 if (verify
!= IMPORT_VERIFY_NO
&& !is_checksum_file(fn
) && signature_style_from_filename(fn
) < 0) {
283 _cleanup_free_
char *checksum_url
= NULL
;
284 const char *suffixed
= NULL
;
286 /* Queue jobs for the checksum file for the image. */
289 suffixed
= strjoina(fn
, ".sha256"); /* Start with the suse-style checksum (if there's a base filename) */
291 suffixed
= "SHA256SUMS";
293 r
= import_url_change_last_component(url
, suffixed
, &checksum_url
);
297 r
= pull_job_new(&checksum_job
, checksum_url
, glue
, userdata
);
301 checksum_job
->on_finished
= on_finished
;
302 checksum_job
->uncompressed_max
= checksum_job
->compressed_max
= 1ULL * 1024ULL * 1024ULL;
303 checksum_job
->on_not_found
= pull_job_restart_with_sha256sum
; /* if this fails, look for ubuntu-style checksum */
306 if (verify
== IMPORT_VERIFY_SIGNATURE
&& signature_style_from_filename(fn
) < 0) {
307 _cleanup_free_
char *signature_url
= NULL
;
308 const char *suffixed
= NULL
;
311 suffixed
= strjoina(fn
, ".sha256.asc"); /* Start with the suse-style checksum (if there's a base filename) */
313 suffixed
= "SHA256SUMS.gpg";
315 /* Queue job for the signature file for the image. */
316 r
= import_url_change_last_component(url
, suffixed
, &signature_url
);
320 r
= pull_job_new(&signature_job
, signature_url
, glue
, userdata
);
324 signature_job
->on_finished
= on_finished
;
325 signature_job
->uncompressed_max
= signature_job
->compressed_max
= 1ULL * 1024ULL * 1024ULL;
326 signature_job
->on_not_found
= pull_job_restart_with_signature
;
329 *ret_checksum_job
= TAKE_PTR(checksum_job
);
330 *ret_signature_job
= TAKE_PTR(signature_job
);
334 static int verify_one(PullJob
*checksum_job
, PullJob
*job
) {
335 _cleanup_free_
char *fn
= NULL
;
336 const char *line
, *p
;
339 assert(checksum_job
);
344 assert(IN_SET(job
->state
, PULL_JOB_DONE
, PULL_JOB_FAILED
));
346 /* Don't verify the checksum if we didn't actually successfully download something new */
347 if (job
->state
!= PULL_JOB_DONE
)
351 if (job
->etag_exists
)
354 assert(job
->calc_checksum
);
355 assert(job
->checksum
);
357 r
= import_url_last_component(job
->url
, &fn
);
359 return log_error_errno(r
, "Failed to extract filename from URL '%s': %m", job
->url
);
361 if (!filename_is_valid(fn
))
362 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG
),
363 "Cannot verify checksum, could not determine server-side file name.");
365 if (is_checksum_file(fn
) || signature_style_from_filename(fn
) >= 0) /* We cannot verify checksum files or signature files with a checksum file */
366 return log_error_errno(SYNTHETIC_ERRNO(ELOOP
),
367 "Cannot verify checksum/signature files via themselves.");
369 line
= strjoina(job
->checksum
, " *", fn
, "\n"); /* string for binary mode */
370 p
= memmem_safe(checksum_job
->payload
,
371 checksum_job
->payload_size
,
375 line
= strjoina(job
->checksum
, " ", fn
, "\n"); /* string for text mode */
376 p
= memmem_safe(checksum_job
->payload
,
377 checksum_job
->payload_size
,
382 /* Only counts if found at beginning of a line */
383 if (!p
|| (p
!= (char*) checksum_job
->payload
&& p
[-1] != '\n'))
384 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG
),
385 "DOWNLOAD INVALID: Checksum of %s file did not check out, file has been tampered with.", fn
);
387 log_info("SHA256 checksum of %s is valid.", job
->url
);
391 static int verify_gpg(
392 const void *payload
, size_t payload_size
,
393 const void *signature
, size_t signature_size
) {
395 _cleanup_close_pair_
int gpg_pipe
[2] = EBADF_PAIR
;
396 _cleanup_(rm_rf_physical_and_freep
) char *gpg_home
= NULL
;
397 char sig_file_path
[] = "/tmp/sigXXXXXX";
398 _cleanup_(sigkill_waitp
) pid_t pid
= 0;
401 assert(payload
|| payload_size
== 0);
402 assert(signature
|| signature_size
== 0);
404 r
= pipe2(gpg_pipe
, O_CLOEXEC
);
406 return log_error_errno(errno
, "Failed to create pipe for gpg: %m");
408 if (signature_size
> 0) {
409 _cleanup_close_
int sig_file
= -EBADF
;
411 sig_file
= mkostemp(sig_file_path
, O_RDWR
);
413 return log_error_errno(errno
, "Failed to create temporary file: %m");
415 r
= loop_write(sig_file
, signature
, signature_size
);
417 log_error_errno(r
, "Failed to write to temporary file: %m");
422 r
= mkdtemp_malloc("/tmp/gpghomeXXXXXX", &gpg_home
);
424 log_error_errno(r
, "Failed to create temporary home for gpg: %m");
428 r
= safe_fork_full("(gpg)",
429 (int[]) { gpg_pipe
[0], -EBADF
, STDERR_FILENO
},
431 FORK_RESET_SIGNALS
|FORK_CLOSE_ALL_FDS
|FORK_DEATHSIG_SIGTERM
|FORK_REARRANGE_STDIO
|FORK_LOG
|FORK_RLIMIT_NOFILE_SAFE
,
436 const char *cmd
[] = {
439 "--no-default-keyring",
440 "--no-auto-key-locate",
441 "--no-auto-check-trustdb",
443 "--trust-model=always",
444 NULL
, /* --homedir= */
445 NULL
, /* --keyring= */
447 NULL
, /* signature file */
449 NULL
/* trailing NULL */
451 size_t k
= ELEMENTSOF(cmd
) - 6;
455 cmd
[k
++] = strjoina("--homedir=", gpg_home
);
457 /* We add the user keyring only to the command line arguments, if it's around since gpg fails
459 if (access(USER_KEYRING_PATH
, F_OK
) >= 0)
460 cmd
[k
++] = "--keyring=" USER_KEYRING_PATH
;
461 else if (access(USER_KEYRING_PATH_LEGACY
, F_OK
) >= 0)
462 cmd
[k
++] = "--keyring=" USER_KEYRING_PATH_LEGACY
;
464 cmd
[k
++] = "--keyring=" VENDOR_KEYRING_PATH
;
466 cmd
[k
++] = "--verify";
468 cmd
[k
++] = sig_file_path
;
473 execvp("gpg2", (char * const *) cmd
);
474 execvp("gpg", (char * const *) cmd
);
475 log_error_errno(errno
, "Failed to execute gpg: %m");
479 gpg_pipe
[0] = safe_close(gpg_pipe
[0]);
481 r
= loop_write(gpg_pipe
[1], payload
, payload_size
);
483 log_error_errno(r
, "Failed to write to pipe: %m");
487 gpg_pipe
[1] = safe_close(gpg_pipe
[1]);
489 r
= wait_for_terminate_and_check("gpg", TAKE_PID(pid
), WAIT_LOG_ABNORMAL
);
492 if (r
!= EXIT_SUCCESS
)
493 r
= log_error_errno(SYNTHETIC_ERRNO(EBADMSG
),
494 "DOWNLOAD INVALID: Signature verification failed.");
496 log_info("Signature verification succeeded.");
501 if (signature_size
> 0)
502 (void) unlink(sig_file_path
);
507 int pull_verify(ImportVerify verify
,
508 const char *checksum
, /* Verify with literal checksum */
510 PullJob
*checksum_job
,
511 PullJob
*signature_job
,
512 PullJob
*settings_job
,
513 PullJob
*roothash_job
,
514 PullJob
*roothash_signature_job
,
515 PullJob
*verity_job
) {
517 _cleanup_free_
char *fn
= NULL
;
518 VerificationStyle style
;
522 assert(verify
== _IMPORT_VERIFY_INVALID
|| verify
< _IMPORT_VERIFY_MAX
);
523 assert(verify
== _IMPORT_VERIFY_INVALID
|| verify
>= 0);
524 assert((verify
< 0) || !checksum
);
526 assert(main_job
->state
== PULL_JOB_DONE
);
528 if (verify
== IMPORT_VERIFY_NO
) /* verification turned off */
532 /* Verification by literal checksum */
533 assert(!checksum_job
);
534 assert(!signature_job
);
535 assert(!settings_job
);
536 assert(!roothash_job
);
537 assert(!roothash_signature_job
);
540 assert(main_job
->calc_checksum
);
541 assert(main_job
->checksum
);
543 if (!strcaseeq(checksum
, main_job
->checksum
))
544 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG
),
545 "DOWNLOAD INVALID: Checksum of %s file did not check out, file has been tampered with.",
551 r
= import_url_last_component(main_job
->url
, &fn
);
553 return log_error_errno(r
, "Failed to extract filename from URL '%s': %m", main_job
->url
);
555 if (signature_style_from_filename(fn
) >= 0)
556 return log_error_errno(SYNTHETIC_ERRNO(ELOOP
),
557 "Main download is a signature file, can't verify it.");
559 if (is_checksum_file(fn
)) {
560 log_debug("Main download is a checksum file, can't validate its checksum with itself, skipping.");
561 verify_job
= main_job
;
563 assert(main_job
->calc_checksum
);
564 assert(main_job
->checksum
);
565 assert(checksum_job
);
566 assert(checksum_job
->state
== PULL_JOB_DONE
);
568 if (!checksum_job
->payload
|| checksum_job
->payload_size
<= 0)
569 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG
),
570 "Checksum is empty, cannot verify.");
573 FOREACH_ARGUMENT(j
, main_job
, settings_job
, roothash_job
, roothash_signature_job
, verity_job
) {
574 r
= verify_one(checksum_job
, j
);
579 verify_job
= checksum_job
;
582 if (verify
!= IMPORT_VERIFY_SIGNATURE
)
587 r
= verification_style_from_url(verify_job
->url
, &style
);
589 return log_error_errno(r
, "Failed to determine verification style from URL '%s': %m", verify_job
->url
);
591 assert(signature_job
);
592 assert(signature_job
->state
== PULL_JOB_DONE
);
594 if (!signature_job
->payload
|| signature_job
->payload_size
<= 0)
595 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG
),
596 "Signature is empty, cannot verify.");
598 return verify_gpg(verify_job
->payload
, verify_job
->payload_size
, signature_job
->payload
, signature_job
->payload_size
);
601 int verification_style_from_url(const char *url
, VerificationStyle
*ret
) {
602 _cleanup_free_
char *last
= NULL
;
608 /* Determines which kind of verification style is appropriate for this url */
610 r
= import_url_last_component(url
, &last
);
614 if (streq(last
, "SHA256SUMS")) {
615 *ret
= VERIFICATION_PER_DIRECTORY
;
619 if (endswith(last
, ".sha256")) {
620 *ret
= VERIFICATION_PER_FILE
;
627 int pull_job_restart_with_sha256sum(PullJob
*j
, char **ret
) {
628 VerificationStyle style
;
633 /* Generic implementation of a PullJobNotFound handler, that restarts the job requesting SHA256SUMS */
635 r
= verification_style_from_url(j
->url
, &style
);
637 return log_error_errno(r
, "Failed to determine verification style of URL '%s': %m", j
->url
);
639 if (style
== VERIFICATION_PER_DIRECTORY
) { /* Nothing to do anymore */
644 assert(style
== VERIFICATION_PER_FILE
); /* This must have been .sha256 style URL before */
646 log_debug("Got 404 for '%s', now trying to get SHA256SUMS instead.", j
->url
);
648 r
= import_url_change_last_component(j
->url
, "SHA256SUMS", ret
);
650 return log_error_errno(r
, "Failed to replace SHA256SUMS suffix: %m");
655 int signature_style_from_url(const char *url
, SignatureStyle
*ret
, char **ret_filename
) {
656 _cleanup_free_
char *last
= NULL
;
657 SignatureStyle style
;
662 assert(ret_filename
);
664 /* Determines which kind of signature style is appropriate for this url */
666 r
= import_url_last_component(url
, &last
);
670 style
= signature_style_from_filename(last
);
674 *ret_filename
= TAKE_PTR(last
);
679 int pull_job_restart_with_signature(PullJob
*j
, char **ret
) {
680 _cleanup_free_
char *last
= NULL
;
681 SignatureStyle style
;
686 /* Generic implementation of a PullJobNotFound handler, that restarts the job requesting a different
687 * signature file. After the initial file, additional *.sha256.gpg, SHA256SUMS.gpg and SHA256SUMS.asc
688 * are tried in this order. */
690 r
= signature_style_from_url(j
->url
, &style
, &last
);
692 return log_error_errno(r
, "Failed to determine signature style of URL '%s': %m", j
->url
);
696 case SIGNATURE_ASC_PER_DIRECTORY
: /* Nothing to do anymore */
700 case SIGNATURE_ASC_PER_FILE
: { /* Try .sha256.gpg next */
703 log_debug("Got 404 for '%s', now trying to get .sha256.gpg instead.", j
->url
);
705 ext
= endswith(last
, ".asc");
709 r
= import_url_change_last_component(j
->url
, last
, ret
);
711 return log_error_errno(r
, "Failed to replace .sha256.asc suffix: %m");
715 case SIGNATURE_GPG_PER_FILE
: /* Try SHA256SUMS.gpg next */
716 log_debug("Got 404 for '%s', now trying to get SHA256SUMS.gpg instead.", j
->url
);
717 r
= import_url_change_last_component(j
->url
, "SHA256SUMS.gpg", ret
);
719 return log_error_errno(r
, "Failed to replace SHA256SUMS suffix: %m");
722 case SIGNATURE_GPG_PER_DIRECTORY
:
723 log_debug("Got 404 for '%s', now trying to get SHA256SUMS.asc instead.", j
->url
);
724 r
= import_url_change_last_component(j
->url
, "SHA256SUMS.asc", ret
);
726 return log_error_errno(r
, "Failed to replace SHA256SUMS.gpg suffix: %m");
730 assert_not_reached();
736 bool pull_validate_local(const char *name
, ImportFlags flags
) {
738 if (FLAGS_SET(flags
, IMPORT_DIRECT
))
739 return path_is_valid(name
);
741 return image_name_is_valid(name
);
744 int pull_url_needs_checksum(const char *url
) {
745 _cleanup_free_
char *fn
= NULL
;
748 /* Returns true if we need to validate this resource via a hash value. This returns true for all
749 * files — except for gpg signature files and SHA256SUMS files and the like, which are validated with
750 * a validation tool like gpg. */
752 r
= import_url_last_component(url
, &fn
);
753 if (r
== -EADDRNOTAVAIL
) /* no last component? then let's assume it's not a signature/checksum file */
758 return !is_checksum_file(fn
) && signature_style_from_filename(fn
) < 0;