]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/import/pull-common.c
tree-wide: introduce new safe_fork() helper and port everything over
[thirdparty/systemd.git] / src / import / pull-common.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
56ebfaf1
LP
2/***
3 This file is part of systemd.
4
5 Copyright 2015 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19***/
20
98c38001
LP
21#include <sys/prctl.h>
22
b5efdb8a 23#include "alloc-util.h"
56ebfaf1 24#include "btrfs-util.h"
430f0182 25#include "capability-util.h"
4f5dd394 26#include "copy.h"
a0956174 27#include "dirent-util.h"
4f5dd394 28#include "escape.h"
3ffd4af2 29#include "fd-util.h"
c004493c 30#include "io-util.h"
bb15fafe 31#include "path-util.h"
0b452006 32#include "process-util.h"
3ffd4af2 33#include "pull-common.h"
4f5dd394
LP
34#include "pull-job.h"
35#include "rm-rf.h"
24882e06 36#include "signal-util.h"
9818005d 37#include "siphash24.h"
07630cea 38#include "string-util.h"
4f5dd394
LP
39#include "strv.h"
40#include "util.h"
49cf4170 41#include "web-util.h"
56ebfaf1
LP
42
43#define FILENAME_ESCAPE "/.#\"\'"
9818005d 44#define HASH_URL_THRESHOLD_LENGTH (_POSIX_PATH_MAX - 16)
56ebfaf1 45
9854730b
LP
46int pull_find_old_etags(
47 const char *url,
48 const char *image_root,
49 int dt,
50 const char *prefix,
51 const char *suffix,
52 char ***etags) {
53
56ebfaf1
LP
54 _cleanup_free_ char *escaped_url = NULL;
55 _cleanup_closedir_ DIR *d = NULL;
56 _cleanup_strv_free_ char **l = NULL;
57 struct dirent *de;
58 int r;
59
60 assert(url);
61 assert(etags);
62
63 if (!image_root)
64 image_root = "/var/lib/machines";
65
66 escaped_url = xescape(url, FILENAME_ESCAPE);
67 if (!escaped_url)
68 return -ENOMEM;
69
70 d = opendir(image_root);
71 if (!d) {
72 if (errno == ENOENT) {
73 *etags = NULL;
74 return 0;
75 }
76
77 return -errno;
78 }
79
80 FOREACH_DIRENT_ALL(de, d, return -errno) {
81 const char *a, *b;
82 char *u;
83
84 if (de->d_type != DT_UNKNOWN &&
85 de->d_type != dt)
86 continue;
87
88 if (prefix) {
89 a = startswith(de->d_name, prefix);
90 if (!a)
91 continue;
92 } else
93 a = de->d_name;
94
95 a = startswith(a, escaped_url);
96 if (!a)
97 continue;
98
99 a = startswith(a, ".");
100 if (!a)
101 continue;
102
103 if (suffix) {
104 b = endswith(de->d_name, suffix);
105 if (!b)
106 continue;
107 } else
108 b = strchr(de->d_name, 0);
109
110 if (a >= b)
111 continue;
112
527b7a42
LP
113 r = cunescape_length(a, b - a, 0, &u);
114 if (r < 0)
115 return r;
56ebfaf1
LP
116
117 if (!http_etag_is_valid(u)) {
118 free(u);
119 continue;
120 }
121
122 r = strv_consume(&l, u);
123 if (r < 0)
124 return r;
125 }
126
127 *etags = l;
128 l = NULL;
129
130 return 0;
131}
132
dc2c282b 133int pull_make_local_copy(const char *final, const char *image_root, const char *local, bool force_local) {
56ebfaf1
LP
134 const char *p;
135 int r;
136
137 assert(final);
138 assert(local);
139
140 if (!image_root)
141 image_root = "/var/lib/machines";
142
63c372cb 143 p = strjoina(image_root, "/", local);
56ebfaf1 144
d9e2daaf
LP
145 if (force_local)
146 (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
56ebfaf1 147
17cbb288
LP
148 r = btrfs_subvol_snapshot(final, p,
149 BTRFS_SNAPSHOT_QUOTA|
150 BTRFS_SNAPSHOT_FALLBACK_COPY|
151 BTRFS_SNAPSHOT_FALLBACK_DIRECTORY|
152 BTRFS_SNAPSHOT_RECURSIVE);
153 if (r < 0)
56ebfaf1
LP
154 return log_error_errno(r, "Failed to create local image: %m");
155
156 log_info("Created new local image '%s'.", local);
157
158 return 0;
159}
160
9818005d
JS
161static int hash_url(const char *url, char **ret) {
162 uint64_t h;
163 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);
164
165 assert(url);
166
933f9cae 167 h = siphash24(url, strlen(url), k.bytes);
9818005d
JS
168 if (asprintf(ret, "%"PRIx64, h) < 0)
169 return -ENOMEM;
170
171 return 0;
172}
173
dc2c282b 174int pull_make_path(const char *url, const char *etag, const char *image_root, const char *prefix, const char *suffix, char **ret) {
9818005d 175 _cleanup_free_ char *escaped_url = NULL, *escaped_etag = NULL;
56ebfaf1
LP
176 char *path;
177
178 assert(url);
179 assert(ret);
180
181 if (!image_root)
182 image_root = "/var/lib/machines";
183
184 escaped_url = xescape(url, FILENAME_ESCAPE);
185 if (!escaped_url)
186 return -ENOMEM;
187
188 if (etag) {
56ebfaf1
LP
189 escaped_etag = xescape(etag, FILENAME_ESCAPE);
190 if (!escaped_etag)
191 return -ENOMEM;
9818005d 192 }
56ebfaf1 193
9818005d 194 path = strjoin(image_root, "/", strempty(prefix), escaped_url, escaped_etag ? "." : "",
4600a396 195 strempty(escaped_etag), strempty(suffix));
56ebfaf1
LP
196 if (!path)
197 return -ENOMEM;
198
9818005d
JS
199 /* URLs might make the path longer than the maximum allowed length for a file name.
200 * When that happens, a URL hash is used instead. Paths returned by this function
201 * can be later used with tempfn_random() which adds 16 bytes to the resulting name. */
202 if (strlen(path) >= HASH_URL_THRESHOLD_LENGTH) {
203 _cleanup_free_ char *hash = NULL;
204 int r;
205
206 free(path);
207
208 r = hash_url(url, &hash);
209 if (r < 0)
210 return r;
211
212 path = strjoin(image_root, "/", strempty(prefix), hash, escaped_etag ? "." : "",
4600a396 213 strempty(escaped_etag), strempty(suffix));
9818005d
JS
214 if (!path)
215 return -ENOMEM;
216 }
217
56ebfaf1
LP
218 *ret = path;
219 return 0;
220}
85dbc41d 221
91359193 222int pull_make_auxiliary_job(
9854730b
LP
223 PullJob **ret,
224 const char *url,
91359193
LP
225 int (*strip_suffixes)(const char *name, char **ret),
226 const char *suffix,
9854730b
LP
227 CurlGlue *glue,
228 PullJobFinished on_finished,
229 void *userdata) {
230
91359193 231 _cleanup_free_ char *last_component = NULL, *ll = NULL, *auxiliary_url = NULL;
9854730b
LP
232 _cleanup_(pull_job_unrefp) PullJob *job = NULL;
233 const char *q;
234 int r;
235
236 assert(ret);
237 assert(url);
91359193 238 assert(strip_suffixes);
9854730b
LP
239 assert(glue);
240
241 r = import_url_last_component(url, &last_component);
242 if (r < 0)
243 return r;
244
91359193 245 r = strip_suffixes(last_component, &ll);
9854730b
LP
246 if (r < 0)
247 return r;
248
91359193 249 q = strjoina(ll, suffix);
9854730b 250
91359193 251 r = import_url_change_last_component(url, q, &auxiliary_url);
9854730b
LP
252 if (r < 0)
253 return r;
254
91359193 255 r = pull_job_new(&job, auxiliary_url, glue, userdata);
9854730b
LP
256 if (r < 0)
257 return r;
258
259 job->on_finished = on_finished;
260 job->compressed_max = job->uncompressed_max = 1ULL * 1024ULL * 1024ULL;
261
262 *ret = job;
263 job = NULL;
264
265 return 0;
266}
267
dc2c282b
LP
268int pull_make_verification_jobs(
269 PullJob **ret_checksum_job,
270 PullJob **ret_signature_job,
98c38001
LP
271 ImportVerify verify,
272 const char *url,
273 CurlGlue *glue,
dc2c282b 274 PullJobFinished on_finished,
98c38001
LP
275 void *userdata) {
276
dc2c282b 277 _cleanup_(pull_job_unrefp) PullJob *checksum_job = NULL, *signature_job = NULL;
98c38001 278 int r;
697be0be 279 const char *chksums = NULL;
98c38001
LP
280
281 assert(ret_checksum_job);
282 assert(ret_signature_job);
283 assert(verify >= 0);
284 assert(verify < _IMPORT_VERIFY_MAX);
285 assert(url);
286 assert(glue);
287
288 if (verify != IMPORT_VERIFY_NO) {
697be0be 289 _cleanup_free_ char *checksum_url = NULL, *fn = NULL;
98c38001 290
697be0be
TB
291 /* Queue jobs for the checksum file for the image. */
292 r = import_url_last_component(url, &fn);
293 if (r < 0)
294 return r;
295
296 chksums = strjoina(fn, ".sha256");
297
298 r = import_url_change_last_component(url, chksums, &checksum_url);
98c38001
LP
299 if (r < 0)
300 return r;
301
dc2c282b 302 r = pull_job_new(&checksum_job, checksum_url, glue, userdata);
98c38001
LP
303 if (r < 0)
304 return r;
305
306 checksum_job->on_finished = on_finished;
307 checksum_job->uncompressed_max = checksum_job->compressed_max = 1ULL * 1024ULL * 1024ULL;
308 }
309
310 if (verify == IMPORT_VERIFY_SIGNATURE) {
311 _cleanup_free_ char *signature_url = NULL;
312
313 /* Queue job for the SHA256SUMS.gpg file for the image. */
314 r = import_url_change_last_component(url, "SHA256SUMS.gpg", &signature_url);
315 if (r < 0)
316 return r;
317
dc2c282b 318 r = pull_job_new(&signature_job, signature_url, glue, userdata);
98c38001
LP
319 if (r < 0)
320 return r;
321
322 signature_job->on_finished = on_finished;
323 signature_job->uncompressed_max = signature_job->compressed_max = 1ULL * 1024ULL * 1024ULL;
324 }
325
326 *ret_checksum_job = checksum_job;
327 *ret_signature_job = signature_job;
328
329 checksum_job = signature_job = NULL;
330
331 return 0;
332}
333
91359193 334static int verify_one(PullJob *checksum_job, PullJob *job) {
98c38001 335 _cleanup_free_ char *fn = NULL;
91359193 336 const char *line, *p;
98c38001
LP
337 int r;
338
91359193 339 assert(checksum_job);
98c38001 340
91359193 341 if (!job)
98c38001
LP
342 return 0;
343
91359193 344 assert(IN_SET(job->state, PULL_JOB_DONE, PULL_JOB_FAILED));
98c38001 345
91359193
LP
346 /* Don't verify the checksum if we didn't actually successfully download something new */
347 if (job->state != PULL_JOB_DONE)
348 return 0;
349 if (job->error != 0)
350 return 0;
351 if (job->etag_exists)
352 return 0;
98c38001 353
91359193
LP
354 assert(job->calc_checksum);
355 assert(job->checksum);
356
357 r = import_url_last_component(job->url, &fn);
98c38001
LP
358 if (r < 0)
359 return log_oom();
360
361 if (!filename_is_valid(fn)) {
91359193 362 log_error("Cannot verify checksum, could not determine server-side file name.");
98c38001
LP
363 return -EBADMSG;
364 }
365
91359193 366 line = strjoina(job->checksum, " *", fn, "\n");
98c38001
LP
367
368 p = memmem(checksum_job->payload,
369 checksum_job->payload_size,
370 line,
371 strlen(line));
372
697be0be
TB
373 if (!p) {
374 line = strjoina(job->checksum, " ", fn, "\n");
375
376 p = memmem(checksum_job->payload,
377 checksum_job->payload_size,
378 line,
379 strlen(line));
380 }
381
98c38001 382 if (!p || (p != (char*) checksum_job->payload && p[-1] != '\n')) {
91359193 383 log_error("DOWNLOAD INVALID: Checksum of %s file did not checkout, file has been tampered with.", fn);
98c38001
LP
384 return -EBADMSG;
385 }
386
91359193
LP
387 log_info("SHA256 checksum of %s is valid.", job->url);
388 return 1;
389}
98c38001 390
91359193
LP
391int pull_verify(PullJob *main_job,
392 PullJob *roothash_job,
393 PullJob *settings_job,
394 PullJob *checksum_job,
395 PullJob *signature_job) {
9854730b 396
91359193 397 _cleanup_close_pair_ int gpg_pipe[2] = { -1, -1 };
91359193
LP
398 _cleanup_close_ int sig_file = -1;
399 char sig_file_path[] = "/tmp/sigXXXXXX", gpg_home[] = "/tmp/gpghomeXXXXXX";
400 _cleanup_(sigkill_waitp) pid_t pid = 0;
401 bool gpg_home_created = false;
402 int r;
9854730b 403
91359193
LP
404 assert(main_job);
405 assert(main_job->state == PULL_JOB_DONE);
9854730b 406
91359193
LP
407 if (!checksum_job)
408 return 0;
9854730b 409
91359193
LP
410 assert(main_job->calc_checksum);
411 assert(main_job->checksum);
9854730b 412
91359193 413 assert(checksum_job->state == PULL_JOB_DONE);
9854730b 414
91359193
LP
415 if (!checksum_job->payload || checksum_job->payload_size <= 0) {
416 log_error("Checksum is empty, cannot verify.");
417 return -EBADMSG;
418 }
9854730b 419
91359193
LP
420 r = verify_one(checksum_job, main_job);
421 if (r < 0)
422 return r;
9854730b 423
91359193
LP
424 r = verify_one(checksum_job, roothash_job);
425 if (r < 0)
426 return r;
9854730b 427
91359193
LP
428 r = verify_one(checksum_job, settings_job);
429 if (r < 0)
430 return r;
9854730b 431
98c38001
LP
432 if (!signature_job)
433 return 0;
434
697be0be
TB
435 if (checksum_job->style == VERIFICATION_PER_FILE)
436 signature_job = checksum_job;
437
dc2c282b 438 assert(signature_job->state == PULL_JOB_DONE);
98c38001
LP
439
440 if (!signature_job->payload || signature_job->payload_size <= 0) {
441 log_error("Signature is empty, cannot verify.");
442 return -EBADMSG;
443 }
444
445 r = pipe2(gpg_pipe, O_CLOEXEC);
446 if (r < 0)
0100b6e1 447 return log_error_errno(errno, "Failed to create pipe for gpg: %m");
98c38001
LP
448
449 sig_file = mkostemp(sig_file_path, O_RDWR);
450 if (sig_file < 0)
451 return log_error_errno(errno, "Failed to create temporary file: %m");
452
453 r = loop_write(sig_file, signature_job->payload, signature_job->payload_size, false);
454 if (r < 0) {
455 log_error_errno(r, "Failed to write to temporary file: %m");
456 goto finish;
457 }
458
0acfdffe
LP
459 if (!mkdtemp(gpg_home)) {
460 r = log_error_errno(errno, "Failed to create tempory home for gpg: %m");
461 goto finish;
462 }
463
464 gpg_home_created = true;
465
4c253ed1
LP
466 r = safe_fork("(gpg)", FORK_RESET_SIGNALS|FORK_DEATHSIG, &pid);
467 if (r < 0)
468 return log_error_errno(r, "Failed to fork off gpg: %m");
469 if (r == 0) {
98c38001
LP
470 const char *cmd[] = {
471 "gpg",
472 "--no-options",
473 "--no-default-keyring",
474 "--no-auto-key-locate",
475 "--no-auto-check-trustdb",
476 "--batch",
477 "--trust-model=always",
0acfdffe
LP
478 NULL, /* --homedir= */
479 NULL, /* --keyring= */
98c38001
LP
480 NULL, /* --verify */
481 NULL, /* signature file */
482 NULL, /* dash */
483 NULL /* trailing NULL */
484 };
0acfdffe 485 unsigned k = ELEMENTSOF(cmd) - 6;
98c38001
LP
486 int null_fd;
487
488 /* Child */
489
98c38001
LP
490 gpg_pipe[1] = safe_close(gpg_pipe[1]);
491
046a82c1
LP
492 r = move_fd(gpg_pipe[0], STDIN_FILENO, false);
493 if (r < 0) {
494 log_error_errno(errno, "Failed to move fd: %m");
98c38001
LP
495 _exit(EXIT_FAILURE);
496 }
497
98c38001
LP
498 null_fd = open("/dev/null", O_WRONLY|O_NOCTTY);
499 if (null_fd < 0) {
500 log_error_errno(errno, "Failed to open /dev/null: %m");
501 _exit(EXIT_FAILURE);
502 }
503
046a82c1
LP
504 r = move_fd(null_fd, STDOUT_FILENO, false);
505 if (r < 0) {
506 log_error_errno(errno, "Failed to move fd: %m");
98c38001
LP
507 _exit(EXIT_FAILURE);
508 }
509
0acfdffe
LP
510 cmd[k++] = strjoina("--homedir=", gpg_home);
511
98c38001
LP
512 /* We add the user keyring only to the command line
513 * arguments, if it's around since gpg fails
514 * otherwise. */
515 if (access(USER_KEYRING_PATH, F_OK) >= 0)
516 cmd[k++] = "--keyring=" USER_KEYRING_PATH;
1c49d1ba
LP
517 else
518 cmd[k++] = "--keyring=" VENDOR_KEYRING_PATH;
98c38001
LP
519
520 cmd[k++] = "--verify";
697be0be
TB
521 if (checksum_job->style == VERIFICATION_PER_DIRECTORY) {
522 cmd[k++] = sig_file_path;
523 cmd[k++] = "-";
524 cmd[k++] = NULL;
525 }
98c38001 526
913f38e4 527 stdio_unset_cloexec();
3d7415f4 528
0acfdffe 529 execvp("gpg2", (char * const *) cmd);
98c38001
LP
530 execvp("gpg", (char * const *) cmd);
531 log_error_errno(errno, "Failed to execute gpg: %m");
532 _exit(EXIT_FAILURE);
533 }
534
535 gpg_pipe[0] = safe_close(gpg_pipe[0]);
536
537 r = loop_write(gpg_pipe[1], checksum_job->payload, checksum_job->payload_size, false);
538 if (r < 0) {
539 log_error_errno(r, "Failed to write to pipe: %m");
540 goto finish;
541 }
542
543 gpg_pipe[1] = safe_close(gpg_pipe[1]);
544
545 r = wait_for_terminate_and_warn("gpg", pid, true);
546 pid = 0;
547 if (r < 0)
548 goto finish;
549 if (r > 0) {
9854730b 550 log_error("DOWNLOAD INVALID: Signature verification failed.");
98c38001
LP
551 r = -EBADMSG;
552 } else {
553 log_info("Signature verification succeeded.");
554 r = 0;
555 }
556
557finish:
558 if (sig_file >= 0)
9854730b 559 (void) unlink(sig_file_path);
98c38001 560
0acfdffe 561 if (gpg_home_created)
c6878637 562 (void) rm_rf(gpg_home, REMOVE_ROOT|REMOVE_PHYSICAL);
0acfdffe 563
98c38001
LP
564 return r;
565}