1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <sys/prctl.h>
4 
5 #include "alloc-util.h"
6 #include "btrfs-util.h"
7 #include "capability-util.h"
8 #include "copy.h"
9 #include "dirent-util.h"
10 #include "escape.h"
11 #include "fd-util.h"
12 #include "hostname-util.h"
13 #include "io-util.h"
14 #include "memory-util.h"
15 #include "path-util.h"
16 #include "process-util.h"
17 #include "pull-common.h"
18 #include "pull-job.h"
19 #include "rlimit-util.h"
20 #include "rm-rf.h"
21 #include "signal-util.h"
22 #include "siphash24.h"
23 #include "string-util.h"
24 #include "strv.h"
25 #include "util.h"
26 #include "web-util.h"
27 
28 #define FILENAME_ESCAPE "/.#\"\'"
29 #define HASH_URL_THRESHOLD_LENGTH (_POSIX_PATH_MAX - 16)
30 
pull_find_old_etags(const char * url,const char * image_root,int dt,const char * prefix,const char * suffix,char *** etags)31 int pull_find_old_etags(
32                 const char *url,
33                 const char *image_root,
34                 int dt,
35                 const char *prefix,
36                 const char *suffix,
37                 char ***etags) {
38 
39         int r;
40 
41         assert(url);
42         assert(etags);
43 
44         if (!image_root)
45                 image_root = "/var/lib/machines";
46 
47         _cleanup_free_ char *escaped_url = xescape(url, FILENAME_ESCAPE);
48         if (!escaped_url)
49                 return -ENOMEM;
50 
51         _cleanup_closedir_ DIR *d = opendir(image_root);
52         if (!d) {
53                 if (errno == ENOENT) {
54                         *etags = NULL;
55                         return 0;
56                 }
57 
58                 return -errno;
59         }
60 
61         _cleanup_strv_free_ char **ans = NULL;
62 
63         FOREACH_DIRENT_ALL(de, d, return -errno) {
64                 _cleanup_free_ char *u = NULL;
65                 const char *a, *b;
66 
67                 if (de->d_type != DT_UNKNOWN &&
68                     de->d_type != dt)
69                         continue;
70 
71                 if (prefix) {
72                         a = startswith(de->d_name, prefix);
73                         if (!a)
74                                 continue;
75                 } else
76                         a = de->d_name;
77 
78                 a = startswith(a, escaped_url);
79                 if (!a)
80                         continue;
81 
82                 a = startswith(a, ".");
83                 if (!a)
84                         continue;
85 
86                 if (suffix) {
87                         b = endswith(de->d_name, suffix);
88                         if (!b)
89                                 continue;
90                 } else
91                         b = strchr(de->d_name, 0);
92 
93                 if (a >= b)
94                         continue;
95 
96                 ssize_t l = cunescape_length(a, b - a, 0, &u);
97                 if (l < 0) {
98                         assert(l >= INT8_MIN);
99                         return l;
100                 }
101 
102                 if (!http_etag_is_valid(u))
103                         continue;
104 
105                 r = strv_consume(&ans, TAKE_PTR(u));
106                 if (r < 0)
107                         return r;
108         }
109 
110         *etags = TAKE_PTR(ans);
111 
112         return 0;
113 }
114 
hash_url(const char * url,char ** ret)115 static int hash_url(const char *url, char **ret) {
116         uint64_t h;
117         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);
118 
119         assert(url);
120 
121         h = siphash24(url, strlen(url), k.bytes);
122         if (asprintf(ret, "%"PRIx64, h) < 0)
123                 return -ENOMEM;
124 
125         return 0;
126 }
127 
pull_make_path(const char * url,const char * etag,const char * image_root,const char * prefix,const char * suffix,char ** ret)128 int pull_make_path(const char *url, const char *etag, const char *image_root, const char *prefix, const char *suffix, char **ret) {
129         _cleanup_free_ char *escaped_url = NULL, *escaped_etag = NULL;
130         char *path;
131 
132         assert(url);
133         assert(ret);
134 
135         if (!image_root)
136                 image_root = "/var/lib/machines";
137 
138         escaped_url = xescape(url, FILENAME_ESCAPE);
139         if (!escaped_url)
140                 return -ENOMEM;
141 
142         if (etag) {
143                 escaped_etag = xescape(etag, FILENAME_ESCAPE);
144                 if (!escaped_etag)
145                         return -ENOMEM;
146         }
147 
148         path = strjoin(image_root, "/", strempty(prefix), escaped_url, escaped_etag ? "." : "",
149                        strempty(escaped_etag), strempty(suffix));
150         if (!path)
151                 return -ENOMEM;
152 
153         /* URLs might make the path longer than the maximum allowed length for a file name.
154          * When that happens, a URL hash is used instead. Paths returned by this function
155          * can be later used with tempfn_random() which adds 16 bytes to the resulting name. */
156         if (strlen(path) >= HASH_URL_THRESHOLD_LENGTH) {
157                 _cleanup_free_ char *hash = NULL;
158                 int r;
159 
160                 free(path);
161 
162                 r = hash_url(url, &hash);
163                 if (r < 0)
164                         return r;
165 
166                 path = strjoin(image_root, "/", strempty(prefix), hash, escaped_etag ? "." : "",
167                                strempty(escaped_etag), strempty(suffix));
168                 if (!path)
169                         return -ENOMEM;
170         }
171 
172         *ret = path;
173         return 0;
174 }
175 
pull_make_auxiliary_job(PullJob ** ret,const char * url,int (* strip_suffixes)(const char * name,char ** ret),const char * suffix,ImportVerify verify,CurlGlue * glue,PullJobOpenDisk on_open_disk,PullJobFinished on_finished,void * userdata)176 int pull_make_auxiliary_job(
177                 PullJob **ret,
178                 const char *url,
179                 int (*strip_suffixes)(const char *name, char **ret),
180                 const char *suffix,
181                 ImportVerify verify,
182                 CurlGlue *glue,
183                 PullJobOpenDisk on_open_disk,
184                 PullJobFinished on_finished,
185                 void *userdata) {
186 
187         _cleanup_free_ char *last_component = NULL, *ll = NULL, *auxiliary_url = NULL;
188         _cleanup_(pull_job_unrefp) PullJob *job = NULL;
189         const char *q;
190         int r;
191 
192         assert(ret);
193         assert(url);
194         assert(strip_suffixes);
195         assert(glue);
196 
197         r = import_url_last_component(url, &last_component);
198         if (r < 0)
199                 return r;
200 
201         r = strip_suffixes(last_component, &ll);
202         if (r < 0)
203                 return r;
204 
205         q = strjoina(ll, suffix);
206 
207         r = import_url_change_last_component(url, q, &auxiliary_url);
208         if (r < 0)
209                 return r;
210 
211         r = pull_job_new(&job, auxiliary_url, glue, userdata);
212         if (r < 0)
213                 return r;
214 
215         job->on_open_disk = on_open_disk;
216         job->on_finished = on_finished;
217         job->compressed_max = job->uncompressed_max = 1ULL * 1024ULL * 1024ULL;
218         job->calc_checksum = IN_SET(verify, IMPORT_VERIFY_CHECKSUM, IMPORT_VERIFY_SIGNATURE);
219 
220         *ret = TAKE_PTR(job);
221         return 0;
222 }
223 
is_checksum_file(const char * fn)224 static bool is_checksum_file(const char *fn) {
225         /* Returns true if the specified filename refers to a checksum file we grok */
226 
227         if (!fn)
228                 return false;
229 
230         return streq(fn, "SHA256SUMS") || endswith(fn, ".sha256");
231 }
232 
is_signature_file(const char * fn)233 static bool is_signature_file(const char *fn) {
234         /* Returns true if the specified filename refers to a signature file we grok (reminder:
235          * suse-style .sha256 files are inline signed) */
236 
237         if (!fn)
238                 return false;
239 
240         return streq(fn, "SHA256SUMS.gpg") || endswith(fn, ".sha256");
241 }
242 
pull_make_verification_jobs(PullJob ** ret_checksum_job,PullJob ** ret_signature_job,ImportVerify verify,const char * checksum,const char * url,CurlGlue * glue,PullJobFinished on_finished,void * userdata)243 int pull_make_verification_jobs(
244                 PullJob **ret_checksum_job,
245                 PullJob **ret_signature_job,
246                 ImportVerify verify,
247                 const char *checksum, /* set if literal checksum verification is requested, in which case 'verify' is set to _IMPORT_VERIFY_INVALID */
248                 const char *url,
249                 CurlGlue *glue,
250                 PullJobFinished on_finished,
251                 void *userdata) {
252 
253         _cleanup_(pull_job_unrefp) PullJob *checksum_job = NULL, *signature_job = NULL;
254         _cleanup_free_ char *fn = NULL;
255         int r;
256 
257         assert(ret_checksum_job);
258         assert(ret_signature_job);
259         assert(verify == _IMPORT_VERIFY_INVALID || verify < _IMPORT_VERIFY_MAX);
260         assert(verify == _IMPORT_VERIFY_INVALID || verify >= 0);
261         assert((verify < 0) || !checksum);
262         assert(url);
263         assert(glue);
264 
265         /* If verification is turned off, or if the checksum to validate is already specified we don't need
266          * to download a checksum file or signature, hence shortcut things */
267         if (verify == IMPORT_VERIFY_NO || checksum) {
268                 *ret_checksum_job = *ret_signature_job = NULL;
269                 return 0;
270         }
271 
272         r = import_url_last_component(url, &fn);
273         if (r < 0 && r != -EADDRNOTAVAIL) /* EADDRNOTAVAIL means there was no last component, which is OK for
274                                            * us, we'll just assume it's not a checksum/signature file */
275                 return r;
276 
277         /* Acquire the checksum file if verification or signature verification is requested and the main file
278          * to acquire isn't a checksum or signature file anyway */
279         if (verify != IMPORT_VERIFY_NO && !is_checksum_file(fn) && !is_signature_file(fn)) {
280                 _cleanup_free_ char *checksum_url = NULL;
281                 const char *suffixed = NULL;
282 
283                 /* Queue jobs for the checksum file for the image. */
284 
285                 if (fn)
286                         suffixed = strjoina(fn, ".sha256"); /* Start with the suse-style checksum (if there's a base filename) */
287                 else
288                         suffixed = "SHA256SUMS";
289 
290                 r = import_url_change_last_component(url, suffixed, &checksum_url);
291                 if (r < 0)
292                         return r;
293 
294                 r = pull_job_new(&checksum_job, checksum_url, glue, userdata);
295                 if (r < 0)
296                         return r;
297 
298                 checksum_job->on_finished = on_finished;
299                 checksum_job->uncompressed_max = checksum_job->compressed_max = 1ULL * 1024ULL * 1024ULL;
300                 checksum_job->on_not_found = pull_job_restart_with_sha256sum; /* if this fails, look for ubuntu-style checksum */
301         }
302 
303         if (verify == IMPORT_VERIFY_SIGNATURE && !is_signature_file(fn)) {
304                 _cleanup_free_ char *signature_url = NULL;
305 
306                 /* Queue job for the SHA256SUMS.gpg file for the image. */
307                 r = import_url_change_last_component(url, "SHA256SUMS.gpg", &signature_url);
308                 if (r < 0)
309                         return r;
310 
311                 r = pull_job_new(&signature_job, signature_url, glue, userdata);
312                 if (r < 0)
313                         return r;
314 
315                 signature_job->on_finished = on_finished;
316                 signature_job->uncompressed_max = signature_job->compressed_max = 1ULL * 1024ULL * 1024ULL;
317         }
318 
319         *ret_checksum_job = TAKE_PTR(checksum_job);
320         *ret_signature_job = TAKE_PTR(signature_job);
321         return 0;
322 }
323 
verify_one(PullJob * checksum_job,PullJob * job)324 static int verify_one(PullJob *checksum_job, PullJob *job) {
325         _cleanup_free_ char *fn = NULL;
326         const char *line, *p;
327         int r;
328 
329         assert(checksum_job);
330 
331         if (!job)
332                 return 0;
333 
334         assert(IN_SET(job->state, PULL_JOB_DONE, PULL_JOB_FAILED));
335 
336         /* Don't verify the checksum if we didn't actually successfully download something new */
337         if (job->state != PULL_JOB_DONE)
338                 return 0;
339         if (job->error != 0)
340                 return 0;
341         if (job->etag_exists)
342                 return 0;
343 
344         assert(job->calc_checksum);
345         assert(job->checksum);
346 
347         r = import_url_last_component(job->url, &fn);
348         if (r < 0)
349                 return log_error_errno(r, "Failed to extract filename from URL '%s': %m", job->url);
350 
351         if (!filename_is_valid(fn))
352                 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
353                                        "Cannot verify checksum, could not determine server-side file name.");
354 
355         if (is_checksum_file(fn) || is_signature_file(fn)) /* We cannot verify checksum files or signature files with a checksum file */
356                 return log_error_errno(SYNTHETIC_ERRNO(ELOOP),
357                                        "Cannot verify checksum/signature files via themselves.");
358 
359         line = strjoina(job->checksum, " *", fn, "\n"); /* string for binary mode */
360         p = memmem_safe(checksum_job->payload,
361                         checksum_job->payload_size,
362                         line,
363                         strlen(line));
364         if (!p) {
365                 line = strjoina(job->checksum, "  ", fn, "\n"); /* string for text mode */
366                 p = memmem_safe(checksum_job->payload,
367                                 checksum_job->payload_size,
368                                 line,
369                                 strlen(line));
370         }
371 
372         /* Only counts if found at beginning of a line */
373         if (!p || (p != (char*) checksum_job->payload && p[-1] != '\n'))
374                 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
375                                        "DOWNLOAD INVALID: Checksum of %s file did not check out, file has been tampered with.", fn);
376 
377         log_info("SHA256 checksum of %s is valid.", job->url);
378         return 1;
379 }
380 
verify_gpg(const void * payload,size_t payload_size,const void * signature,size_t signature_size)381 static int verify_gpg(
382                 const void *payload, size_t payload_size,
383                 const void *signature, size_t signature_size) {
384 
385         _cleanup_close_pair_ int gpg_pipe[2] = { -1, -1 };
386         char sig_file_path[] = "/tmp/sigXXXXXX", gpg_home[] = "/tmp/gpghomeXXXXXX";
387         _cleanup_(sigkill_waitp) pid_t pid = 0;
388         bool gpg_home_created = false;
389         int r;
390 
391         assert(payload || payload_size == 0);
392         assert(signature || signature_size == 0);
393 
394         r = pipe2(gpg_pipe, O_CLOEXEC);
395         if (r < 0)
396                 return log_error_errno(errno, "Failed to create pipe for gpg: %m");
397 
398         if (signature_size > 0) {
399                 _cleanup_close_ int sig_file = -1;
400 
401                 sig_file = mkostemp(sig_file_path, O_RDWR);
402                 if (sig_file < 0)
403                         return log_error_errno(errno, "Failed to create temporary file: %m");
404 
405                 r = loop_write(sig_file, signature, signature_size, false);
406                 if (r < 0) {
407                         log_error_errno(r, "Failed to write to temporary file: %m");
408                         goto finish;
409                 }
410         }
411 
412         if (!mkdtemp(gpg_home)) {
413                 r = log_error_errno(errno, "Failed to create temporary home for gpg: %m");
414                 goto finish;
415         }
416 
417         gpg_home_created = true;
418 
419         r = safe_fork("(gpg)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &pid);
420         if (r < 0)
421                 return r;
422         if (r == 0) {
423                 const char *cmd[] = {
424                         "gpg",
425                         "--no-options",
426                         "--no-default-keyring",
427                         "--no-auto-key-locate",
428                         "--no-auto-check-trustdb",
429                         "--batch",
430                         "--trust-model=always",
431                         NULL, /* --homedir=  */
432                         NULL, /* --keyring= */
433                         NULL, /* --verify */
434                         NULL, /* signature file */
435                         NULL, /* dash */
436                         NULL  /* trailing NULL */
437                 };
438                 size_t k = ELEMENTSOF(cmd) - 6;
439 
440                 /* Child */
441 
442                 gpg_pipe[1] = safe_close(gpg_pipe[1]);
443 
444                 r = rearrange_stdio(TAKE_FD(gpg_pipe[0]), -1, STDERR_FILENO);
445                 if (r < 0) {
446                         log_error_errno(r, "Failed to rearrange stdin/stdout: %m");
447                         _exit(EXIT_FAILURE);
448                 }
449 
450                 (void) rlimit_nofile_safe();
451 
452                 cmd[k++] = strjoina("--homedir=", gpg_home);
453 
454                 /* We add the user keyring only to the command line arguments, if it's around since gpg fails
455                  * otherwise. */
456                 if (access(USER_KEYRING_PATH, F_OK) >= 0)
457                         cmd[k++] = "--keyring=" USER_KEYRING_PATH;
458                 else
459                         cmd[k++] = "--keyring=" VENDOR_KEYRING_PATH;
460 
461                 cmd[k++] = "--verify";
462                 if (signature) {
463                         cmd[k++] = sig_file_path;
464                         cmd[k++] = "-";
465                         cmd[k++] = NULL;
466                 }
467 
468                 execvp("gpg2", (char * const *) cmd);
469                 execvp("gpg", (char * const *) cmd);
470                 log_error_errno(errno, "Failed to execute gpg: %m");
471                 _exit(EXIT_FAILURE);
472         }
473 
474         gpg_pipe[0] = safe_close(gpg_pipe[0]);
475 
476         r = loop_write(gpg_pipe[1], payload, payload_size, false);
477         if (r < 0) {
478                 log_error_errno(r, "Failed to write to pipe: %m");
479                 goto finish;
480         }
481 
482         gpg_pipe[1] = safe_close(gpg_pipe[1]);
483 
484         r = wait_for_terminate_and_check("gpg", TAKE_PID(pid), WAIT_LOG_ABNORMAL);
485         if (r < 0)
486                 goto finish;
487         if (r != EXIT_SUCCESS)
488                 r = log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
489                                     "DOWNLOAD INVALID: Signature verification failed.");
490         else {
491                 log_info("Signature verification succeeded.");
492                 r = 0;
493         }
494 
495 finish:
496         if (signature_size > 0)
497                 (void) unlink(sig_file_path);
498 
499         if (gpg_home_created)
500                 (void) rm_rf(gpg_home, REMOVE_ROOT|REMOVE_PHYSICAL);
501 
502         return r;
503 }
504 
pull_verify(ImportVerify verify,const char * checksum,PullJob * main_job,PullJob * checksum_job,PullJob * signature_job,PullJob * settings_job,PullJob * roothash_job,PullJob * roothash_signature_job,PullJob * verity_job)505 int pull_verify(ImportVerify verify,
506                 const char *checksum, /* Verify with literal checksum */
507                 PullJob *main_job,
508                 PullJob *checksum_job,
509                 PullJob *signature_job,
510                 PullJob *settings_job,
511                 PullJob *roothash_job,
512                 PullJob *roothash_signature_job,
513                 PullJob *verity_job) {
514 
515         _cleanup_free_ char *fn = NULL;
516         VerificationStyle style;
517         PullJob *verify_job;
518         int r;
519 
520         assert(verify == _IMPORT_VERIFY_INVALID || verify < _IMPORT_VERIFY_MAX);
521         assert(verify == _IMPORT_VERIFY_INVALID || verify >= 0);
522         assert((verify < 0) || !checksum);
523         assert(main_job);
524         assert(main_job->state == PULL_JOB_DONE);
525 
526         if (verify == IMPORT_VERIFY_NO) /* verification turned off */
527                 return 0;
528 
529         if (checksum) {
530                 /* Verification by literal checksum */
531                 assert(!checksum_job);
532                 assert(!signature_job);
533                 assert(!settings_job);
534                 assert(!roothash_job);
535                 assert(!roothash_signature_job);
536                 assert(!verity_job);
537 
538                 assert(main_job->calc_checksum);
539                 assert(main_job->checksum);
540 
541                 if (!strcaseeq(checksum, main_job->checksum))
542                         return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
543                                                "DOWNLOAD INVALID: Checksum of %s file did not check out, file has been tampered with.",
544                                                main_job->url);
545 
546                 return 0;
547         }
548 
549         r = import_url_last_component(main_job->url, &fn);
550         if (r < 0)
551                 return log_error_errno(r, "Failed to extract filename from URL '%s': %m", main_job->url);
552 
553         if (is_signature_file(fn))
554                 return log_error_errno(SYNTHETIC_ERRNO(ELOOP),
555                                        "Main download is a signature file, can't verify it.");
556 
557         if (is_checksum_file(fn)) {
558                 log_debug("Main download is a checksum file, can't validate its checksum with itself, skipping.");
559                 verify_job = main_job;
560         } else {
561                 PullJob *j;
562                 assert(main_job->calc_checksum);
563                 assert(main_job->checksum);
564                 assert(checksum_job);
565                 assert(checksum_job->state == PULL_JOB_DONE);
566 
567                 if (!checksum_job->payload || checksum_job->payload_size <= 0)
568                         return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
569                                                "Checksum is empty, cannot verify.");
570 
571                 FOREACH_POINTER(j, main_job, settings_job, roothash_job, roothash_signature_job, verity_job) {
572                         r = verify_one(checksum_job, j);
573                         if (r < 0)
574                                 return r;
575                 }
576 
577                 verify_job = checksum_job;
578         }
579 
580         if (verify != IMPORT_VERIFY_SIGNATURE)
581                 return 0;
582 
583         assert(verify_job);
584 
585         r = verification_style_from_url(verify_job->url, &style);
586         if (r < 0)
587                 return log_error_errno(r, "Failed to determine verification style from URL '%s': %m", verify_job->url);
588 
589         if (style == VERIFICATION_PER_DIRECTORY) {
590                 assert(signature_job);
591                 assert(signature_job->state == PULL_JOB_DONE);
592 
593                 if (!signature_job->payload || signature_job->payload_size <= 0)
594                         return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
595                                                "Signature is empty, cannot verify.");
596 
597                 return verify_gpg(verify_job->payload, verify_job->payload_size, signature_job->payload, signature_job->payload_size);
598         } else
599                 return verify_gpg(verify_job->payload, verify_job->payload_size, NULL, 0);
600 }
601 
verification_style_from_url(const char * url,VerificationStyle * ret)602 int verification_style_from_url(const char *url, VerificationStyle *ret) {
603         _cleanup_free_ char *last = NULL;
604         int r;
605 
606         assert(url);
607         assert(ret);
608 
609         /* Determines which kind of verification style is appropriate for this url */
610 
611         r = import_url_last_component(url, &last);
612         if (r < 0)
613                 return r;
614 
615         if (streq(last, "SHA256SUMS")) {
616                 *ret = VERIFICATION_PER_DIRECTORY;
617                 return 0;
618         }
619 
620         if (endswith(last, ".sha256")) {
621                 *ret = VERIFICATION_PER_FILE;
622                 return 0;
623         }
624 
625         return -EINVAL;
626 }
627 
pull_job_restart_with_sha256sum(PullJob * j,char ** ret)628 int pull_job_restart_with_sha256sum(PullJob *j, char **ret) {
629         VerificationStyle style;
630         int r;
631 
632         assert(j);
633 
634         /* Generic implementation of a PullJobNotFound handler, that restarts the job requesting SHA256SUMS */
635 
636         r = verification_style_from_url(j->url, &style);
637         if (r < 0)
638                 return log_error_errno(r, "Failed to determine verification style of URL '%s': %m", j->url);
639 
640         if (style == VERIFICATION_PER_DIRECTORY) /* Nothing to do anymore */
641                 return 0;
642 
643         assert(style == VERIFICATION_PER_FILE); /* This must have been .sha256 style URL before */
644 
645         log_debug("Got 404 for %s, now trying to get SHA256SUMS instead.", j->url);
646 
647         r = import_url_change_last_component(j->url, "SHA256SUMS", ret);
648         if (r < 0)
649                 return log_error_errno(r, "Failed to replace SHA256SUMS suffix: %m");
650 
651         return 1;
652 }
653 
pull_validate_local(const char * name,PullFlags flags)654 bool pull_validate_local(const char *name, PullFlags flags) {
655 
656         if (FLAGS_SET(flags, PULL_DIRECT))
657                 return path_is_valid(name);
658 
659         return hostname_is_valid(name, 0);
660 }
661 
pull_url_needs_checksum(const char * url)662 int pull_url_needs_checksum(const char *url) {
663         _cleanup_free_ char *fn = NULL;
664         int r;
665 
666         /* Returns true if we need to validate this resource via a hash value. This returns true for all
667          * files — except for gpg signature files and SHA256SUMS files and the like, which are validated with
668          * a validation tool like gpg. */
669 
670         r = import_url_last_component(url, &fn);
671         if (r == -EADDRNOTAVAIL) /* no last component? then let's assume it's not a signature/checksum file */
672                 return false;
673         if (r < 0)
674                 return r;
675 
676         return !is_checksum_file(fn) && !is_signature_file(fn);
677 }
678