]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/import/pull-tar.c
import: properly verify roothash_signature + verity download, too
[thirdparty/systemd.git] / src / import / pull-tar.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
56ebfaf1 2
56ebfaf1 3#include <curl/curl.h>
cf0fbc49 4#include <sys/prctl.h>
56ebfaf1 5
7079cfef 6#include "sd-daemon.h"
07630cea 7
b5efdb8a 8#include "alloc-util.h"
56ebfaf1 9#include "btrfs-util.h"
07630cea
LP
10#include "copy.h"
11#include "curl-util.h"
3ffd4af2 12#include "fd-util.h"
f4f15635 13#include "fs-util.h"
07630cea
LP
14#include "hostname-util.h"
15#include "import-common.h"
16#include "import-util.h"
56ebfaf1
LP
17#include "macro.h"
18#include "mkdir.h"
26166c88 19#include "path-util.h"
25300b5a 20#include "process-util.h"
dc2c282b 21#include "pull-common.h"
07630cea 22#include "pull-job.h"
3ffd4af2 23#include "pull-tar.h"
07630cea
LP
24#include "rm-rf.h"
25#include "string-util.h"
26#include "strv.h"
e4de7287 27#include "tmpfile-util.h"
07630cea
LP
28#include "utf8.h"
29#include "util.h"
49cf4170 30#include "web-util.h"
56ebfaf1 31
7079cfef
LP
32typedef enum TarProgress {
33 TAR_DOWNLOADING,
34 TAR_VERIFYING,
35 TAR_FINALIZING,
36 TAR_COPYING,
37} TarProgress;
38
dc2c282b 39struct TarPull {
56ebfaf1
LP
40 sd_event *event;
41 CurlGlue *glue;
42
133b34f6
LP
43 PullFlags flags;
44 ImportVerify verify;
56ebfaf1
LP
45 char *image_root;
46
dc2c282b
LP
47 PullJob *tar_job;
48 PullJob *checksum_job;
49 PullJob *signature_job;
133b34f6 50 PullJob *settings_job;
56ebfaf1 51
dc2c282b 52 TarPullFinished on_finished;
56ebfaf1
LP
53 void *userdata;
54
56ebfaf1 55 char *local;
56ebfaf1
LP
56
57 pid_t tar_pid;
58
56ebfaf1 59 char *final_path;
9854730b
LP
60 char *temp_path;
61
62 char *settings_path;
63 char *settings_temp_path;
56ebfaf1
LP
64};
65
dc2c282b 66TarPull* tar_pull_unref(TarPull *i) {
56ebfaf1
LP
67 if (!i)
68 return NULL;
69
0100b6e1 70 if (i->tar_pid > 1) {
8b71fce8 71 (void) kill_and_sigcont(i->tar_pid, SIGKILL);
0100b6e1 72 (void) wait_for_terminate(i->tar_pid, NULL);
56ebfaf1
LP
73 }
74
dc2c282b
LP
75 pull_job_unref(i->tar_job);
76 pull_job_unref(i->checksum_job);
77 pull_job_unref(i->signature_job);
133b34f6 78 pull_job_unref(i->settings_job);
56ebfaf1
LP
79
80 curl_glue_unref(i->glue);
81 sd_event_unref(i->event);
82
133b34f6
LP
83 rm_rf_subvolume_and_free(i->temp_path);
84 unlink_and_free(i->settings_temp_path);
9854730b 85
56ebfaf1 86 free(i->final_path);
9854730b 87 free(i->settings_path);
56ebfaf1
LP
88 free(i->image_root);
89 free(i->local);
e0061812 90
6b430fdb 91 return mfree(i);
56ebfaf1
LP
92}
93
dc2c282b
LP
94int tar_pull_new(
95 TarPull **ret,
8b71fce8
LP
96 sd_event *event,
97 const char *image_root,
dc2c282b 98 TarPullFinished on_finished,
8b71fce8
LP
99 void *userdata) {
100
0d94088e
YW
101 _cleanup_(curl_glue_unrefp) CurlGlue *g = NULL;
102 _cleanup_(sd_event_unrefp) sd_event *e = NULL;
dc2c282b 103 _cleanup_(tar_pull_unrefp) TarPull *i = NULL;
0d94088e 104 _cleanup_free_ char *root = NULL;
56ebfaf1
LP
105 int r;
106
107 assert(ret);
56ebfaf1 108
0d94088e
YW
109 root = strdup(image_root ?: "/var/lib/machines");
110 if (!root)
56ebfaf1
LP
111 return -ENOMEM;
112
9854730b 113 if (event)
0d94088e 114 e = sd_event_ref(event);
9854730b 115 else {
0d94088e 116 r = sd_event_default(&e);
9854730b
LP
117 if (r < 0)
118 return r;
119 }
56ebfaf1 120
0d94088e 121 r = curl_glue_new(&g, e);
56ebfaf1
LP
122 if (r < 0)
123 return r;
124
0d94088e
YW
125 i = new(TarPull, 1);
126 if (!i)
127 return -ENOMEM;
128
129 *i = (TarPull) {
130 .on_finished = on_finished,
131 .userdata = userdata,
132 .image_root = TAKE_PTR(root),
0d94088e
YW
133 .event = TAKE_PTR(e),
134 .glue = TAKE_PTR(g),
135 };
136
dc2c282b 137 i->glue->on_finished = pull_job_curl_on_finished;
56ebfaf1
LP
138 i->glue->userdata = i;
139
1cc6c93a 140 *ret = TAKE_PTR(i);
56ebfaf1
LP
141
142 return 0;
143}
144
dc2c282b 145static void tar_pull_report_progress(TarPull *i, TarProgress p) {
7079cfef
LP
146 unsigned percent;
147
148 assert(i);
149
150 switch (p) {
151
152 case TAR_DOWNLOADING: {
153 unsigned remain = 85;
154
155 percent = 0;
156
157 if (i->checksum_job) {
158 percent += i->checksum_job->progress_percent * 5 / 100;
159 remain -= 5;
160 }
161
162 if (i->signature_job) {
163 percent += i->signature_job->progress_percent * 5 / 100;
164 remain -= 5;
165 }
166
133b34f6
LP
167 if (i->settings_job) {
168 percent += i->settings_job->progress_percent * 5 / 100;
169 remain -= 5;
170 }
171
7079cfef
LP
172 if (i->tar_job)
173 percent += i->tar_job->progress_percent * remain / 100;
174 break;
175 }
176
177 case TAR_VERIFYING:
178 percent = 85;
179 break;
180
181 case TAR_FINALIZING:
182 percent = 90;
183 break;
184
185 case TAR_COPYING:
186 percent = 95;
187 break;
188
189 default:
190 assert_not_reached("Unknown progress state");
191 }
192
193 sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent);
194 log_debug("Combined progress %u%%", percent);
195}
196
91359193
LP
197static int tar_pull_determine_path(TarPull *i, const char *suffix, char **field) {
198 int r;
199
200 assert(i);
201 assert(field);
202
203 if (*field)
204 return 0;
205
206 assert(i->tar_job);
207
208 r = pull_make_path(i->tar_job->url, i->tar_job->etag, i->image_root, ".tar-", suffix, field);
209 if (r < 0)
210 return log_oom();
211
212 return 1;
213}
214
dc2c282b 215static int tar_pull_make_local_copy(TarPull *i) {
0d6e763b
LP
216 int r;
217
218 assert(i);
219 assert(i->tar_job);
220
221 if (!i->local)
222 return 0;
223
133b34f6 224 r = pull_make_local_copy(i->final_path, i->image_root, i->local, i->flags);
0100b6e1
LP
225 if (r < 0)
226 return r;
227
133b34f6 228 if (FLAGS_SET(i->flags, PULL_SETTINGS)) {
9854730b
LP
229 const char *local_settings;
230 assert(i->settings_job);
231
91359193
LP
232 r = tar_pull_determine_path(i, ".nspawn", &i->settings_path);
233 if (r < 0)
234 return r;
9854730b
LP
235
236 local_settings = strjoina(i->image_root, "/", i->local, ".nspawn");
237
133b34f6 238 r = copy_file_atomic(i->settings_path, local_settings, 0664, 0, 0, COPY_REFLINK | (FLAGS_SET(i->flags, PULL_FORCE) ? COPY_REPLACE : 0));
9854730b
LP
239 if (r == -EEXIST)
240 log_warning_errno(r, "Settings file %s already exists, not replacing.", local_settings);
33859a6b
LP
241 else if (r == -ENOENT)
242 log_debug_errno(r, "Skipping creation of settings file, since none was found.");
243 else if (r < 0)
79b6198b
LP
244 log_warning_errno(r, "Failed to copy settings files %s, ignoring: %m", local_settings);
245 else
33859a6b 246 log_info("Created new settings file %s.", local_settings);
9854730b
LP
247 }
248
0d6e763b
LP
249 return 0;
250}
251
dc2c282b 252static bool tar_pull_is_done(TarPull *i) {
8b71fce8
LP
253 assert(i);
254 assert(i->tar_job);
255
9854730b 256 if (!PULL_JOB_IS_COMPLETE(i->tar_job))
8b71fce8 257 return false;
9854730b
LP
258 if (i->checksum_job && !PULL_JOB_IS_COMPLETE(i->checksum_job))
259 return false;
260 if (i->signature_job && !PULL_JOB_IS_COMPLETE(i->signature_job))
8b71fce8 261 return false;
133b34f6
LP
262 if (i->settings_job && !PULL_JOB_IS_COMPLETE(i->settings_job))
263 return false;
8b71fce8
LP
264
265 return true;
266}
267
dc2c282b
LP
268static void tar_pull_job_on_finished(PullJob *j) {
269 TarPull *i;
56ebfaf1
LP
270 int r;
271
272 assert(j);
273 assert(j->userdata);
274
275 i = j->userdata;
9854730b
LP
276
277 if (j == i->settings_job) {
278 if (j->error != 0)
279 log_info_errno(j->error, "Settings file could not be retrieved, proceeding without.");
697be0be 280 } else if (j->error != 0 && j != i->signature_job) {
0100b6e1
LP
281 if (j == i->checksum_job)
282 log_error_errno(j->error, "Failed to retrieve SHA256 checksum, cannot verify. (Try --verify=no?)");
0100b6e1
LP
283 else
284 log_error_errno(j->error, "Failed to retrieve image file. (Wrong URL?)");
285
56ebfaf1
LP
286 r = j->error;
287 goto finish;
288 }
289
c33e405f
LP
290 /* This is invoked if either the download completed successfully, or the download was skipped because
291 * we already have the etag. */
56ebfaf1 292
dc2c282b 293 if (!tar_pull_is_done(i))
0100b6e1
LP
294 return;
295
f14717a7
LP
296 if (i->signature_job && i->signature_job->error != 0) {
297 VerificationStyle style;
697be0be 298
f14717a7
LP
299 r = verification_style_from_url(i->checksum_job->url, &style);
300 if (r < 0) {
301 log_error_errno(r, "Failed to determine verification style from checksum URL: %m");
302 goto finish;
303 }
304
305 if (style == VERIFICATION_PER_DIRECTORY) { /* A failed signature file download only matters
306 * in per-directory verification mode, since only
307 * then the signature is detached, and thus a file
308 * of its own. */
309 log_error_errno(j->error, "Failed to retrieve signature file, cannot verify. (Try --verify=no?)");
310 r = i->signature_job->error;
311 goto finish;
312 }
697be0be
TB
313 }
314
9854730b
LP
315 i->tar_job->disk_fd = safe_close(i->tar_job->disk_fd);
316 if (i->settings_job)
317 i->settings_job->disk_fd = safe_close(i->settings_job->disk_fd);
56ebfaf1 318
91359193
LP
319 r = tar_pull_determine_path(i, NULL, &i->final_path);
320 if (r < 0)
321 goto finish;
322
56ebfaf1 323 if (i->tar_pid > 0) {
7d4904fe 324 r = wait_for_terminate_and_check("tar", i->tar_pid, WAIT_LOG);
56ebfaf1
LP
325 i->tar_pid = 0;
326 if (r < 0)
327 goto finish;
b4a34311 328 if (r != EXIT_SUCCESS) {
9854730b
LP
329 r = -EIO;
330 goto finish;
331 }
56ebfaf1
LP
332 }
333
0100b6e1
LP
334 if (!i->tar_job->etag_exists) {
335 /* This is a new download, verify it, and move it into place */
336
dc2c282b 337 tar_pull_report_progress(i, TAR_VERIFYING);
7079cfef 338
ff2f7797
LP
339 r = pull_verify(i->verify,
340 i->tar_job,
341 i->checksum_job,
342 i->signature_job,
343 i->settings_job,
344 /* roothash_job = */ NULL,
345 /* roothash_signature_job = */ NULL,
346 /* verity_job = */ NULL);
0100b6e1
LP
347 if (r < 0)
348 goto finish;
349
dc2c282b 350 tar_pull_report_progress(i, TAR_FINALIZING);
7079cfef 351
c33e405f
LP
352 r = import_mangle_os_tree(i->temp_path);
353 if (r < 0)
354 goto finish;
355
b6e676ce 356 r = import_make_read_only(i->temp_path);
56ebfaf1
LP
357 if (r < 0)
358 goto finish;
359
f85ef957
AC
360 r = rename_noreplace(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path);
361 if (r < 0) {
dc38f65a 362 log_error_errno(r, "Failed to rename to final image name to %s: %m", i->final_path);
56ebfaf1
LP
363 goto finish;
364 }
56ebfaf1 365
9854730b
LP
366 i->temp_path = mfree(i->temp_path);
367
368 if (i->settings_job &&
91359193 369 i->settings_job->error == 0) {
9854730b 370
e0061812 371 /* Also move the settings file into place, if it exists. Note that we do so only if we also
91359193 372 * moved the tar file in place, to keep things strictly in sync. */
e0061812 373 assert(i->settings_temp_path);
91359193 374
e0061812
LP
375 /* Regenerate final name for this auxiliary file, we might know the etag of the file now, and
376 * we should incorporate it in the file name if we can */
91359193 377 i->settings_path = mfree(i->settings_path);
e0061812 378
91359193
LP
379 r = tar_pull_determine_path(i, ".nspawn", &i->settings_path);
380 if (r < 0)
381 goto finish;
9854730b
LP
382
383 r = import_make_read_only(i->settings_temp_path);
384 if (r < 0)
385 goto finish;
386
387 r = rename_noreplace(AT_FDCWD, i->settings_temp_path, AT_FDCWD, i->settings_path);
388 if (r < 0) {
dc38f65a 389 log_error_errno(r, "Failed to rename settings file to %s: %m", i->settings_path);
9854730b
LP
390 goto finish;
391 }
392
393 i->settings_temp_path = mfree(i->settings_temp_path);
394 }
56ebfaf1
LP
395 }
396
dc2c282b 397 tar_pull_report_progress(i, TAR_COPYING);
7079cfef 398
dc2c282b 399 r = tar_pull_make_local_copy(i);
0d6e763b
LP
400 if (r < 0)
401 goto finish;
402
56ebfaf1
LP
403 r = 0;
404
405finish:
56ebfaf1
LP
406 if (i->on_finished)
407 i->on_finished(i, r, i->userdata);
408 else
409 sd_event_exit(i->event, r);
410}
411
9854730b 412static int tar_pull_job_on_open_disk_tar(PullJob *j) {
dc2c282b 413 TarPull *i;
56ebfaf1
LP
414 int r;
415
416 assert(j);
417 assert(j->userdata);
418
419 i = j->userdata;
8b71fce8 420 assert(i->tar_job == j);
8b71fce8 421 assert(i->tar_pid <= 0);
56ebfaf1 422
91359193
LP
423 if (!i->temp_path) {
424 r = tempfn_random_child(i->image_root, "tar", &i->temp_path);
425 if (r < 0)
426 return log_oom();
427 }
56ebfaf1
LP
428
429 mkdir_parents_label(i->temp_path, 0700);
430
82c4440d
LP
431 r = btrfs_subvol_make_fallback(i->temp_path, 0755);
432 if (r < 0)
433 return log_error_errno(r, "Failed to create directory/subvolume %s: %m", i->temp_path);
052ba0eb
LP
434 if (r > 0) { /* actually btrfs subvol */
435 (void) import_assign_pool_quota_and_warn(i->image_root);
8c9cfc28 436 (void) import_assign_pool_quota_and_warn(i->temp_path);
052ba0eb 437 }
56ebfaf1 438
587fec42 439 j->disk_fd = import_fork_tar_x(i->temp_path, &i->tar_pid);
2c140ded
LP
440 if (j->disk_fd < 0)
441 return j->disk_fd;
56ebfaf1
LP
442
443 return 0;
444}
445
9854730b
LP
446static int tar_pull_job_on_open_disk_settings(PullJob *j) {
447 TarPull *i;
448 int r;
449
450 assert(j);
451 assert(j->userdata);
452
453 i = j->userdata;
454 assert(i->settings_job == j);
9854730b 455
91359193
LP
456 if (!i->settings_temp_path) {
457 r = tempfn_random_child(i->image_root, "settings", &i->settings_temp_path);
458 if (r < 0)
459 return log_oom();
460 }
9854730b
LP
461
462 mkdir_parents_label(i->settings_temp_path, 0700);
463
464 j->disk_fd = open(i->settings_temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664);
465 if (j->disk_fd < 0)
466 return log_error_errno(errno, "Failed to create %s: %m", i->settings_temp_path);
467
468 return 0;
469}
470
dc2c282b
LP
471static void tar_pull_job_on_progress(PullJob *j) {
472 TarPull *i;
7079cfef
LP
473
474 assert(j);
475 assert(j->userdata);
476
477 i = j->userdata;
478
dc2c282b 479 tar_pull_report_progress(i, TAR_DOWNLOADING);
7079cfef
LP
480}
481
9854730b
LP
482int tar_pull_start(
483 TarPull *i,
484 const char *url,
485 const char *local,
133b34f6
LP
486 PullFlags flags,
487 ImportVerify verify) {
9854730b 488
56ebfaf1
LP
489 int r;
490
491 assert(i);
9854730b
LP
492 assert(verify < _IMPORT_VERIFY_MAX);
493 assert(verify >= 0);
133b34f6 494 assert(!(flags & ~PULL_FLAGS_MASK_TAR));
56ebfaf1 495
56ebfaf1
LP
496 if (!http_url_is_valid(url))
497 return -EINVAL;
498
52ef5dd7 499 if (local && !hostname_is_valid(local, 0))
56ebfaf1
LP
500 return -EINVAL;
501
8b71fce8
LP
502 if (i->tar_job)
503 return -EBUSY;
504
56ebfaf1
LP
505 r = free_and_strdup(&i->local, local);
506 if (r < 0)
507 return r;
9854730b 508
133b34f6 509 i->flags = flags;
0100b6e1 510 i->verify = verify;
56ebfaf1 511
9854730b 512 /* Set up download job for TAR file */
dc2c282b 513 r = pull_job_new(&i->tar_job, url, i->glue, i);
56ebfaf1
LP
514 if (r < 0)
515 return r;
516
dc2c282b 517 i->tar_job->on_finished = tar_pull_job_on_finished;
9854730b 518 i->tar_job->on_open_disk = tar_pull_job_on_open_disk_tar;
dc2c282b 519 i->tar_job->on_progress = tar_pull_job_on_progress;
0100b6e1 520 i->tar_job->calc_checksum = verify != IMPORT_VERIFY_NO;
56ebfaf1 521
dc2c282b 522 r = pull_find_old_etags(url, i->image_root, DT_DIR, ".tar-", NULL, &i->tar_job->old_etags);
56ebfaf1
LP
523 if (r < 0)
524 return r;
525
133b34f6
LP
526 /* Set up download of checksum/signature files */
527 r = pull_make_verification_jobs(&i->checksum_job, &i->signature_job, verify, url, i->glue, tar_pull_job_on_finished, i);
528 if (r < 0)
529 return r;
530
9854730b 531 /* Set up download job for the settings file (.nspawn) */
133b34f6 532 if (FLAGS_SET(flags, PULL_SETTINGS)) {
91359193 533 r = pull_make_auxiliary_job(&i->settings_job, url, tar_strip_suffixes, ".nspawn", i->glue, tar_pull_job_on_finished, i);
9854730b
LP
534 if (r < 0)
535 return r;
536
537 i->settings_job->on_open_disk = tar_pull_job_on_open_disk_settings;
538 i->settings_job->on_progress = tar_pull_job_on_progress;
539 i->settings_job->calc_checksum = verify != IMPORT_VERIFY_NO;
9854730b
LP
540 }
541
dc2c282b 542 r = pull_job_begin(i->tar_job);
0100b6e1
LP
543 if (r < 0)
544 return r;
545
546 if (i->checksum_job) {
dc2c282b 547 i->checksum_job->on_progress = tar_pull_job_on_progress;
f14717a7 548 i->checksum_job->on_not_found = pull_job_restart_with_sha256sum;
7079cfef 549
dc2c282b 550 r = pull_job_begin(i->checksum_job);
0100b6e1
LP
551 if (r < 0)
552 return r;
553 }
554
555 if (i->signature_job) {
dc2c282b 556 i->signature_job->on_progress = tar_pull_job_on_progress;
7079cfef 557
dc2c282b 558 r = pull_job_begin(i->signature_job);
0100b6e1
LP
559 if (r < 0)
560 return r;
561 }
562
133b34f6
LP
563 if (i->settings_job) {
564 r = pull_job_begin(i->settings_job);
565 if (r < 0)
566 return r;
567 }
568
0100b6e1 569 return 0;
56ebfaf1 570}