]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/import/pull-common.c
tree-wide: use TAKE_PTR() and TAKE_FD() macros
[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
ae2a15bc 127 *etags = TAKE_PTR(l);
56ebfaf1
LP
128
129 return 0;
130}
131
dc2c282b 132int pull_make_local_copy(const char *final, const char *image_root, const char *local, bool force_local) {
56ebfaf1
LP
133 const char *p;
134 int r;
135
136 assert(final);
137 assert(local);
138
139 if (!image_root)
140 image_root = "/var/lib/machines";
141
63c372cb 142 p = strjoina(image_root, "/", local);
56ebfaf1 143
d9e2daaf
LP
144 if (force_local)
145 (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
56ebfaf1 146
17cbb288
LP
147 r = btrfs_subvol_snapshot(final, p,
148 BTRFS_SNAPSHOT_QUOTA|
149 BTRFS_SNAPSHOT_FALLBACK_COPY|
150 BTRFS_SNAPSHOT_FALLBACK_DIRECTORY|
151 BTRFS_SNAPSHOT_RECURSIVE);
152 if (r < 0)
56ebfaf1
LP
153 return log_error_errno(r, "Failed to create local image: %m");
154
155 log_info("Created new local image '%s'.", local);
156
157 return 0;
158}
159
9818005d
JS
160static int hash_url(const char *url, char **ret) {
161 uint64_t h;
162 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);
163
164 assert(url);
165
933f9cae 166 h = siphash24(url, strlen(url), k.bytes);
9818005d
JS
167 if (asprintf(ret, "%"PRIx64, h) < 0)
168 return -ENOMEM;
169
170 return 0;
171}
172
dc2c282b 173int pull_make_path(const char *url, const char *etag, const char *image_root, const char *prefix, const char *suffix, char **ret) {
9818005d 174 _cleanup_free_ char *escaped_url = NULL, *escaped_etag = NULL;
56ebfaf1
LP
175 char *path;
176
177 assert(url);
178 assert(ret);
179
180 if (!image_root)
181 image_root = "/var/lib/machines";
182
183 escaped_url = xescape(url, FILENAME_ESCAPE);
184 if (!escaped_url)
185 return -ENOMEM;
186
187 if (etag) {
56ebfaf1
LP
188 escaped_etag = xescape(etag, FILENAME_ESCAPE);
189 if (!escaped_etag)
190 return -ENOMEM;
9818005d 191 }
56ebfaf1 192
9818005d 193 path = strjoin(image_root, "/", strempty(prefix), escaped_url, escaped_etag ? "." : "",
4600a396 194 strempty(escaped_etag), strempty(suffix));
56ebfaf1
LP
195 if (!path)
196 return -ENOMEM;
197
9818005d
JS
198 /* URLs might make the path longer than the maximum allowed length for a file name.
199 * When that happens, a URL hash is used instead. Paths returned by this function
200 * can be later used with tempfn_random() which adds 16 bytes to the resulting name. */
201 if (strlen(path) >= HASH_URL_THRESHOLD_LENGTH) {
202 _cleanup_free_ char *hash = NULL;
203 int r;
204
205 free(path);
206
207 r = hash_url(url, &hash);
208 if (r < 0)
209 return r;
210
211 path = strjoin(image_root, "/", strempty(prefix), hash, escaped_etag ? "." : "",
4600a396 212 strempty(escaped_etag), strempty(suffix));
9818005d
JS
213 if (!path)
214 return -ENOMEM;
215 }
216
56ebfaf1
LP
217 *ret = path;
218 return 0;
219}
85dbc41d 220
91359193 221int pull_make_auxiliary_job(
9854730b
LP
222 PullJob **ret,
223 const char *url,
91359193
LP
224 int (*strip_suffixes)(const char *name, char **ret),
225 const char *suffix,
9854730b
LP
226 CurlGlue *glue,
227 PullJobFinished on_finished,
228 void *userdata) {
229
91359193 230 _cleanup_free_ char *last_component = NULL, *ll = NULL, *auxiliary_url = NULL;
9854730b
LP
231 _cleanup_(pull_job_unrefp) PullJob *job = NULL;
232 const char *q;
233 int r;
234
235 assert(ret);
236 assert(url);
91359193 237 assert(strip_suffixes);
9854730b
LP
238 assert(glue);
239
240 r = import_url_last_component(url, &last_component);
241 if (r < 0)
242 return r;
243
91359193 244 r = strip_suffixes(last_component, &ll);
9854730b
LP
245 if (r < 0)
246 return r;
247
91359193 248 q = strjoina(ll, suffix);
9854730b 249
91359193 250 r = import_url_change_last_component(url, q, &auxiliary_url);
9854730b
LP
251 if (r < 0)
252 return r;
253
91359193 254 r = pull_job_new(&job, auxiliary_url, glue, userdata);
9854730b
LP
255 if (r < 0)
256 return r;
257
258 job->on_finished = on_finished;
259 job->compressed_max = job->uncompressed_max = 1ULL * 1024ULL * 1024ULL;
260
1cc6c93a 261 *ret = TAKE_PTR(job);
9854730b
LP
262
263 return 0;
264}
265
dc2c282b
LP
266int pull_make_verification_jobs(
267 PullJob **ret_checksum_job,
268 PullJob **ret_signature_job,
98c38001
LP
269 ImportVerify verify,
270 const char *url,
271 CurlGlue *glue,
dc2c282b 272 PullJobFinished on_finished,
98c38001
LP
273 void *userdata) {
274
dc2c282b 275 _cleanup_(pull_job_unrefp) PullJob *checksum_job = NULL, *signature_job = NULL;
98c38001 276 int r;
697be0be 277 const char *chksums = NULL;
98c38001
LP
278
279 assert(ret_checksum_job);
280 assert(ret_signature_job);
281 assert(verify >= 0);
282 assert(verify < _IMPORT_VERIFY_MAX);
283 assert(url);
284 assert(glue);
285
286 if (verify != IMPORT_VERIFY_NO) {
697be0be 287 _cleanup_free_ char *checksum_url = NULL, *fn = NULL;
98c38001 288
697be0be
TB
289 /* Queue jobs for the checksum file for the image. */
290 r = import_url_last_component(url, &fn);
291 if (r < 0)
292 return r;
293
294 chksums = strjoina(fn, ".sha256");
295
296 r = import_url_change_last_component(url, chksums, &checksum_url);
98c38001
LP
297 if (r < 0)
298 return r;
299
dc2c282b 300 r = pull_job_new(&checksum_job, checksum_url, glue, userdata);
98c38001
LP
301 if (r < 0)
302 return r;
303
304 checksum_job->on_finished = on_finished;
305 checksum_job->uncompressed_max = checksum_job->compressed_max = 1ULL * 1024ULL * 1024ULL;
306 }
307
308 if (verify == IMPORT_VERIFY_SIGNATURE) {
309 _cleanup_free_ char *signature_url = NULL;
310
311 /* Queue job for the SHA256SUMS.gpg file for the image. */
312 r = import_url_change_last_component(url, "SHA256SUMS.gpg", &signature_url);
313 if (r < 0)
314 return r;
315
dc2c282b 316 r = pull_job_new(&signature_job, signature_url, glue, userdata);
98c38001
LP
317 if (r < 0)
318 return r;
319
320 signature_job->on_finished = on_finished;
321 signature_job->uncompressed_max = signature_job->compressed_max = 1ULL * 1024ULL * 1024ULL;
322 }
323
324 *ret_checksum_job = checksum_job;
325 *ret_signature_job = signature_job;
326
327 checksum_job = signature_job = NULL;
328
329 return 0;
330}
331
91359193 332static int verify_one(PullJob *checksum_job, PullJob *job) {
98c38001 333 _cleanup_free_ char *fn = NULL;
91359193 334 const char *line, *p;
98c38001
LP
335 int r;
336
91359193 337 assert(checksum_job);
98c38001 338
91359193 339 if (!job)
98c38001
LP
340 return 0;
341
91359193 342 assert(IN_SET(job->state, PULL_JOB_DONE, PULL_JOB_FAILED));
98c38001 343
91359193
LP
344 /* Don't verify the checksum if we didn't actually successfully download something new */
345 if (job->state != PULL_JOB_DONE)
346 return 0;
347 if (job->error != 0)
348 return 0;
349 if (job->etag_exists)
350 return 0;
98c38001 351
91359193
LP
352 assert(job->calc_checksum);
353 assert(job->checksum);
354
355 r = import_url_last_component(job->url, &fn);
98c38001
LP
356 if (r < 0)
357 return log_oom();
358
359 if (!filename_is_valid(fn)) {
91359193 360 log_error("Cannot verify checksum, could not determine server-side file name.");
98c38001
LP
361 return -EBADMSG;
362 }
363
91359193 364 line = strjoina(job->checksum, " *", fn, "\n");
98c38001
LP
365
366 p = memmem(checksum_job->payload,
367 checksum_job->payload_size,
368 line,
369 strlen(line));
370
697be0be
TB
371 if (!p) {
372 line = strjoina(job->checksum, " ", fn, "\n");
373
374 p = memmem(checksum_job->payload,
375 checksum_job->payload_size,
376 line,
377 strlen(line));
378 }
379
98c38001 380 if (!p || (p != (char*) checksum_job->payload && p[-1] != '\n')) {
91359193 381 log_error("DOWNLOAD INVALID: Checksum of %s file did not checkout, file has been tampered with.", fn);
98c38001
LP
382 return -EBADMSG;
383 }
384
91359193
LP
385 log_info("SHA256 checksum of %s is valid.", job->url);
386 return 1;
387}
98c38001 388
91359193
LP
389int pull_verify(PullJob *main_job,
390 PullJob *roothash_job,
391 PullJob *settings_job,
392 PullJob *checksum_job,
393 PullJob *signature_job) {
9854730b 394
91359193 395 _cleanup_close_pair_ int gpg_pipe[2] = { -1, -1 };
91359193
LP
396 _cleanup_close_ int sig_file = -1;
397 char sig_file_path[] = "/tmp/sigXXXXXX", gpg_home[] = "/tmp/gpghomeXXXXXX";
398 _cleanup_(sigkill_waitp) pid_t pid = 0;
399 bool gpg_home_created = false;
400 int r;
9854730b 401
91359193
LP
402 assert(main_job);
403 assert(main_job->state == PULL_JOB_DONE);
9854730b 404
91359193
LP
405 if (!checksum_job)
406 return 0;
9854730b 407
91359193
LP
408 assert(main_job->calc_checksum);
409 assert(main_job->checksum);
9854730b 410
91359193 411 assert(checksum_job->state == PULL_JOB_DONE);
9854730b 412
91359193
LP
413 if (!checksum_job->payload || checksum_job->payload_size <= 0) {
414 log_error("Checksum is empty, cannot verify.");
415 return -EBADMSG;
416 }
9854730b 417
91359193
LP
418 r = verify_one(checksum_job, main_job);
419 if (r < 0)
420 return r;
9854730b 421
91359193
LP
422 r = verify_one(checksum_job, roothash_job);
423 if (r < 0)
424 return r;
9854730b 425
91359193
LP
426 r = verify_one(checksum_job, settings_job);
427 if (r < 0)
428 return r;
9854730b 429
98c38001
LP
430 if (!signature_job)
431 return 0;
432
697be0be
TB
433 if (checksum_job->style == VERIFICATION_PER_FILE)
434 signature_job = checksum_job;
435
dc2c282b 436 assert(signature_job->state == PULL_JOB_DONE);
98c38001
LP
437
438 if (!signature_job->payload || signature_job->payload_size <= 0) {
439 log_error("Signature is empty, cannot verify.");
440 return -EBADMSG;
441 }
442
443 r = pipe2(gpg_pipe, O_CLOEXEC);
444 if (r < 0)
0100b6e1 445 return log_error_errno(errno, "Failed to create pipe for gpg: %m");
98c38001
LP
446
447 sig_file = mkostemp(sig_file_path, O_RDWR);
448 if (sig_file < 0)
449 return log_error_errno(errno, "Failed to create temporary file: %m");
450
451 r = loop_write(sig_file, signature_job->payload, signature_job->payload_size, false);
452 if (r < 0) {
453 log_error_errno(r, "Failed to write to temporary file: %m");
454 goto finish;
455 }
456
0acfdffe
LP
457 if (!mkdtemp(gpg_home)) {
458 r = log_error_errno(errno, "Failed to create tempory home for gpg: %m");
459 goto finish;
460 }
461
462 gpg_home_created = true;
463
b6e1fff1 464 r = safe_fork("(gpg)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &pid);
4c253ed1 465 if (r < 0)
b6e1fff1 466 return r;
4c253ed1 467 if (r == 0) {
98c38001
LP
468 const char *cmd[] = {
469 "gpg",
470 "--no-options",
471 "--no-default-keyring",
472 "--no-auto-key-locate",
473 "--no-auto-check-trustdb",
474 "--batch",
475 "--trust-model=always",
0acfdffe
LP
476 NULL, /* --homedir= */
477 NULL, /* --keyring= */
98c38001
LP
478 NULL, /* --verify */
479 NULL, /* signature file */
480 NULL, /* dash */
481 NULL /* trailing NULL */
482 };
0acfdffe 483 unsigned k = ELEMENTSOF(cmd) - 6;
98c38001
LP
484
485 /* Child */
486
98c38001
LP
487 gpg_pipe[1] = safe_close(gpg_pipe[1]);
488
2b33ab09 489 r = rearrange_stdio(gpg_pipe[0], -1, STDERR_FILENO);
046a82c1 490 if (r < 0) {
2b33ab09 491 log_error_errno(r, "Failed to rearrange stdin/stdout: %m");
98c38001
LP
492 _exit(EXIT_FAILURE);
493 }
494
0acfdffe
LP
495 cmd[k++] = strjoina("--homedir=", gpg_home);
496
98c38001
LP
497 /* We add the user keyring only to the command line
498 * arguments, if it's around since gpg fails
499 * otherwise. */
500 if (access(USER_KEYRING_PATH, F_OK) >= 0)
501 cmd[k++] = "--keyring=" USER_KEYRING_PATH;
1c49d1ba
LP
502 else
503 cmd[k++] = "--keyring=" VENDOR_KEYRING_PATH;
98c38001
LP
504
505 cmd[k++] = "--verify";
697be0be
TB
506 if (checksum_job->style == VERIFICATION_PER_DIRECTORY) {
507 cmd[k++] = sig_file_path;
508 cmd[k++] = "-";
509 cmd[k++] = NULL;
510 }
98c38001 511
0acfdffe 512 execvp("gpg2", (char * const *) cmd);
98c38001
LP
513 execvp("gpg", (char * const *) cmd);
514 log_error_errno(errno, "Failed to execute gpg: %m");
515 _exit(EXIT_FAILURE);
516 }
517
518 gpg_pipe[0] = safe_close(gpg_pipe[0]);
519
520 r = loop_write(gpg_pipe[1], checksum_job->payload, checksum_job->payload_size, false);
521 if (r < 0) {
522 log_error_errno(r, "Failed to write to pipe: %m");
523 goto finish;
524 }
525
526 gpg_pipe[1] = safe_close(gpg_pipe[1]);
527
7d4904fe 528 r = wait_for_terminate_and_check("gpg", pid, WAIT_LOG_ABNORMAL);
98c38001
LP
529 pid = 0;
530 if (r < 0)
531 goto finish;
b4a34311 532 if (r != EXIT_SUCCESS) {
9854730b 533 log_error("DOWNLOAD INVALID: Signature verification failed.");
98c38001
LP
534 r = -EBADMSG;
535 } else {
536 log_info("Signature verification succeeded.");
537 r = 0;
538 }
539
540finish:
541 if (sig_file >= 0)
9854730b 542 (void) unlink(sig_file_path);
98c38001 543
0acfdffe 544 if (gpg_home_created)
c6878637 545 (void) rm_rf(gpg_home, REMOVE_ROOT|REMOVE_PHYSICAL);
0acfdffe 546
98c38001
LP
547 return r;
548}